function varargout = galvoscanner(varargin)
% galvoscanner M-file for galvoscanner.fig
% Control 2 galvo scan mirrors, a beam shutter and camera.
%
% Installation:
%   Extract all files and add directories to Matlab's path
%   Type galvoscanner at command prompt to run
%
% Features:
%   Video interface
%   Manual beam position/exposure controls
%   Programmable beam paths (via scripts)
%   View beam paths superimposed on video (streamed or still images)
%   Trigger path scans on digital hardware channels
%   Correspondence between image and anatomical coordinates
%   Logging of voltages, anatomical coordinates and shutter status
%   Beam diagnostics
%
% Requirements:
%       Image Acquisition Toolbox for Matlab
%       Data Acquisition Toolbox for Matlab
%       Statistics Toolbox for Matlab (for regress function)
%       National Instruments board with minimum (e.g. PCI-6711);
%           3 analog outputs
%           2 timers/counters
%       NIDAQmx installed
%
% IMPORTANT NOTE REGARDING DATARECORDER:
%   If you run galvoscanner and datarecorder concurrently, then both must
%   run in same Matlab instance and galvoscanner must be evoked first.
%
%

% Author:
% Per M Knutsen, UCSD, Physics Dept <pmknutsen@gmail.com>
% April 2011

% Last Modified by GUIDE v2.5 16-May-2011 14:40:34

% Begin initialization code - DO NOT EDIT
gui_Singleton = 1;
gui_State = struct('gui_Name',       mfilename, ...
                   'gui_Singleton',  gui_Singleton, ...
                   'gui_OpeningFcn', @galvoscanner_OpeningFcn, ...
                   'gui_OutputFcn',  @galvoscanner_OutputFcn, ...
                   'gui_LayoutFcn',  [] , ...
                   'gui_Callback',   []);
if nargin && ischar(varargin{1})
    gui_State.gui_Callback = str2func(varargin{1});
end

if nargout
    [varargout{1:nargout}] = gui_mainfcn(gui_State, varargin{:});
else
    gui_mainfcn(gui_State, varargin{:});
end
% End initialization code - DO NOT EDIT

% --- Executes just before galvoscanner is made visible.
function galvoscanner_OpeningFcn(hObject, eventdata, handles, varargin)
% This function has no output args, see OutputFcn.
% hObject    handle to figure
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)
% varargin   command line arguments to galvoscanner (see VARARGIN)

% Choose default command line output for galvoscanner
clc
handles.output = hObject;
set(handles.figure1, 'colormap', gray)

% Update handles structure
guidata(hObject, handles);

% Update list of available NI DAQ boards
tNI = daqhwinfo('nidaq');
cIDs = tNI.InstalledBoardIds;
vValidDev = [];
for b = 1:length(cIDs) % search for NIDAQmx devices (ignore Traditional/Legacy devices)
    if ~isempty(strfind(cIDs{b}, 'Dev')) % NIDAQmx devices are Dev1, Dev2 etc
        vValidDev = [vValidDev b];
    end
end
if isempty(vValidDev)
    uiwait(warndlg('There are no compatible NIDAQmx devices present on this computer. Minimial requirement is a National Instruments board with at least 3 analog output channels (e.g. PCI-6711).'))
    close(handles.figure1)
end
set(handles.galvo_device, 'String', tNI.BoardNames(vValidDev))

% Get list of available camera
UpdateCamList(handles)

% Load default settings from disk
try
    load_defaults_Callback(hObject, eventdata, handles)
catch
    % Enable first device in list by default
    if ~isempty(vValidDev)
        HardwareEnable(handles)
    end
    % Update channel lists
    UpdateChannelLists(handles)
end

% Lock edits
set(handles.lock_channel_edit, 'Value', 1)
lock_channel_edit_Callback(handles.lock_channel_edit, eventdata, handles)

ConfigureOutputChannels(handles)

% Reset galvos to origin
global g_XPos g_YPos g_bShutterPos
g_XPos = 0; g_YPos = 0; g_bShutterPos = 0;
StartAnalogOut(handles)

% Start timer that reads DAQ status
oTimer = timer('StartDelay', 0, 'TasksToExecute', Inf, 'Period', 0.1, ...
    'TimerFcn', @(hObject,eventdata)ReadDAQStatus(hObject,eventdata,handles), ...
    'Name', 'GalvoDAQStatus', ...
    'ExecutionMode', 'fixedDelay');
start(oTimer)

return

% --- Update camera list
function UpdateCamList(handles)
cAll = {};
hImaqInfo = imaqhwinfo;
% WINVIDEO camera devices
if any(strcmpi(hImaqInfo.InstalledAdaptors, 'winvideo'))
    hIMAQ_WIN = imaqhwinfo('winvideo');
    for i = 1:length(hIMAQ_WIN.DeviceInfo)
        cWinDevices = hIMAQ_WIN.DeviceInfo(i).DeviceName;
        cAll{end+1} = [cWinDevices ' (winvideo)'];
    end
end
% NI camera devices
if any(strcmpi(hImaqInfo.InstalledAdaptors, 'ni'))
    hIMAQ_NI = imaqhwinfo('ni');
    if ~isempty(hIMAQ_NI.DeviceInfo)
        cNIDevices = hIMAQ_NI.DeviceInfo.DeviceName;
        cAll{end+1} = [cNIDevices ' (ni)'];
    end
end
set(handles.camera, 'String', cAll);
return

% --- Update channel lists in GUI
function UpdateChannelLists(handles)
global g_GalvoAO
% Analog outputs
if isempty(g_GalvoAO), return, end
vChID = daqhwinfo(g_GalvoAO, 'ChannelIDs');
set(handles.galvo_ychannel, 'string', vChID, 'value', 1);
set(handles.galvo_xchannel, 'string', vChID, 'value', 1);
% Trigger channels
cTrigChIN = {'RTSI0', 'RTSI1', 'RTSI2', 'RTSI3', 'RTSI4', 'RTSI5', 'RTSI 6', 'PFI0', 'PFI1'};
cTrigChOUT = {'RTSI0', 'RTSI1', 'RTSI2', 'RTSI3', 'RTSI4', 'RTSI5', 'RTSI 6', 'CTR 0 OUT', 'CTR 1 OUT'};
set(handles.galvo_trigger_in, 'string', cTrigChIN, 'value', 1);
set(handles.galvo_trigger_out, 'string', cTrigChOUT, 'value', 2);
% Shutter channel
set(handles.shutter_channel, 'string', vChID, 'value', length(vChID));
% Issue warning if there are less than 3 channels
if length(vChID) < 3
    uiwait(warndlg('A minimum of 3 channels are required to independently configure X+Y galvo control and a shutter.'))
end
return

% --- Enable hardware
function HardwareEnable(handles)
global g_GalvoAO g_VideoObj
% NI AO board
if ~isempty(whos('global','g_GalvoAO')) % reset old device
    try stop(g_GalvoAO); end
    try delete(g_GalvoAO); end
end

tNI = daqhwinfo('nidaq');
cIDs = tNI.InstalledBoardIds;
vValidDev = [];
for b = 1:length(cIDs)
    if ~isempty(strfind(cIDs{b}, 'Dev')) % NIDAQmx devices are Dev1, Dev2 etc
        vValidDev = [vValidDev b];
    end
end
nSelDev = get(handles.galvo_device, 'value');
g_GalvoAO = analogoutput('nidaq', cIDs{nSelDev});

% Set trigger callback to log function
set(g_GalvoAO, 'TriggerFcn', @(hObject,eventdata)LogAnalogOutput(hObject,eventdata,handles))

% Camera
if ~isempty(whos('global','g_VideoObj')) % reset old camera device
    try stop(g_VideoObj); end
    try delete(g_VideoObj); end
    clear global g_VideoObj;
end
global g_VideoObj

% Enable camera
nCam = get(handles.camera, 'value');
cCams = get(handles.camera, 'string');
sCamera = cCams{nCam};
nFrom = strfind(sCamera, '(') + 1;
nTo = strfind(sCamera, ')') - 1;
sAdaptor = sCamera(nFrom:nTo);
sCamera = sCamera(1:nFrom-3);
imaqreset
try
    g_VideoObj = videoinput(sAdaptor, sCamera);
    set(g_VideoObj, 'returnedColorSpace', 'grayscale')
    % Display snapshot
    grab_frame_Callback([], [], handles)
catch
    uiwait(warndlg(sprintf('Requested video device is invalid: %s::%s.', sAdaptor, sCamera), 'GalvoScanner Error'))
end
return

% --- Outputs from this function are returned to the command line.
function varargout = galvoscanner_OutputFcn(hObject, eventdata, handles) 
% Get default command line output from handles structure
varargout{1} = handles.output;

% --- Executes on selection change in galvo_device.
function galvo_device_Callback(hObject, eventdata, handles)
% Re-enable hardware
HardwareEnable(handles)    
% Update channel lists
UpdateChannelLists(handles)
return

% --- Executes during object creation, after setting all properties.
function galvo_device_CreateFcn(hObject, eventdata, handles)
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end


% --- Executes on selection change in galvo_trigger_in.
function galvo_trigger_in_Callback(hObject, eventdata, handles)
return

% --- Executes during object creation, after setting all properties.
function galvo_trigger_in_CreateFcn(hObject, eventdata, handles)
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end

% --- Executes on selection change in galvo_xchannel.
function galvo_xchannel_Callback(hObject, eventdata, handles)
if ~CheckChannelSelection(handles), return, end
ConfigureOutputChannels(handles)
return

% --- Executes during object creation, after setting all properties.
function galvo_xchannel_CreateFcn(hObject, eventdata, handles)
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end

% --- Executes on selection change in galvo_ychannel.
function galvo_ychannel_Callback(hObject, eventdata, handles)
if ~CheckChannelSelection(handles), return, end
ConfigureOutputChannels(handles)
return

% --- Check manual channel selection
function bResult = CheckChannelSelection(handles)
nX = get(handles.galvo_xchannel, 'value');
nY = get(handles.galvo_ychannel, 'value');
nSh = get(handles.shutter_channel, 'value');
if (nX == nY || nX == nSh || nY == nSh)
    uiwait(warndlg('You must select different channels for X and Y channels and the shutter.'))
    bResult = 0;
else
    bResult = 1;
end
return

% --- Executes during object creation, after setting all properties.
function galvo_ychannel_CreateFcn(hObject, eventdata, handles)
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end

% --- Executes on button press in manual_right.
function manual_left_Callback(hObject, eventdata, handles)
global g_XPos g_YPos g_bShutterPos
g_XPos = max([-10 g_XPos(end) + str2num(get(handles.galvo_increment, 'string'))]);
g_YPos = g_YPos(end);
g_bShutterPos = g_bShutterPos(end);
StartAnalogOut(handles)
return

% --- Executes on button press in manual_right.
function manual_right_Callback(hObject, eventdata, handles)
global g_XPos g_YPos g_bShutterPos
g_XPos = min([10 g_XPos(end) - str2num(get(handles.galvo_increment, 'string'))]);
g_YPos = g_YPos(end);
g_bShutterPos = g_bShutterPos(end);
StartAnalogOut(handles)
return

% --- Executes on button press in manual_down.
function manual_down_Callback(hObject, eventdata, handles)
global g_YPos g_XPos g_bShutterPos
g_YPos = max([-10 g_YPos(end) - str2num(get(handles.galvo_increment, 'string'))]);
g_XPos = g_XPos(end);
g_bShutterPos = g_bShutterPos(end);
StartAnalogOut(handles)
return


% --- Executes on button press in manual_down.
function manual_up_Callback(hObject, eventdata, handles)
global g_YPos g_XPos g_bShutterPos
g_YPos = min([10 g_YPos(end) + str2num(get(handles.galvo_increment, 'string'))]);
g_XPos = g_XPos(end);
g_bShutterPos = g_bShutterPos(end);
StartAnalogOut(handles)
return

% --- Executes on button press in manual_reset.
function manual_reset_Callback(hObject, eventdata, handles)
global g_XPos g_YPos g_bShutterPos
g_XPos = 0; g_YPos = 0;
g_bShutterPos = g_bShutterPos(end);
StartAnalogOut(handles)
return

% --- Executes on selection change in shutter_channel.
function shutter_channel_Callback(hObject, eventdata, handles)
if ~CheckChannelSelection(handles), return, end
uiwait(warndlg('CAUTION! The output voltage on the shutter/intensity control channel ranges from 0 to 5 V, while the voltage on X-Y galvo channels ranges from -10 to 10 V. Verify therefore that the shutter cable is connected correctly. Connecting the shutter cable by mistake to the X-Y galvo channels may permanently damage the shutter driver unit!'))
ConfigureOutputChannels(handles)
return

% --- Executes during object creation, after setting all properties.
function shutter_channel_CreateFcn(hObject, eventdata, handles)
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end


% --- Executes on button press in trigger_path.
function trigger_path_Callback(hObject, eventdata, handles)
global g_GalvoAO
switch get(handles.trigger_path, 'value')
    case 0
        % Stop trigger repeat timer
        oTimer = timerfind('Name', 'GalvoTriggerRepeatTimer');
        if ~isempty(oTimer)
            stop(oTimer);
            delete(oTimer);
        end
        % Stop device (will stop currently running path)
        if isrunning(g_GalvoAO), stop(g_GalvoAO); end
        % Change TriggerType to immediate
        set(g_GalvoAO, 'TriggerType', 'Immediate')
        set(handles.trigger_path, 'backgroundColor', get(handles.run_path, 'backgroundColor'))
    case 1
        % Initialize scan path
        if ~InitScanPath(handles), return, end % Initialize scan path
        % Configure analog outputs to start on trigger receive
        % NOTE: Only 1 trigger can be executed for analog outputs
        %       To repeat trigger get status of TriggersExecuted and repeat
        cTrigger = get(handles.galvo_trigger_in, 'string');
        sTrigIN = cTrigger{get(handles.galvo_trigger_in, 'value')};
        set(g_GalvoAO, 'TriggerType', 'HwDigital')
        cCh = get(handles.galvo_trigger_in, 'string');
        sCh = cCh{get(handles.galvo_trigger_in, 'value')};
        % Note: Trigger on NegativeEdge slope by default. Setting TriggerCondition
        % to PositiveEdge is not supported on NI boards when HwDigital is used.
        set(g_GalvoAO, 'HwDigitalTriggerSource', sCh)
        % Start engine
        StartAnalogOut(handles)
        % Start timer that queries if trigger has executed and then repeat
        oTimer = timer('StartDelay', 0, 'TasksToExecute', Inf, 'Period', 0.05, ...
            'TimerFcn', @(hObject,eventdata)GetTriggerStatus(hObject,eventdata,handles), ...
            'Name', 'GalvoTriggerRepeatTimer', ...
            'ExecutionMode', 'fixedDelay');
        start(oTimer)
        set(handles.trigger_path, 'backgroundColor', [0 1 0])
end
return

% --- Get trigger status and re-arm trigger
function GetTriggerStatus(hObject,eventdata,handles)
global g_GalvoAO
% Trigger ran and device is no longer sending samples
if ~isrunning(g_GalvoAO) && (get(g_GalvoAO, 'TriggersExecuted') == 1)
    % Re-arm
    trigger_path_Callback(handles.trigger_path, eventdata, handles)
end
return

% --- Transfer loaded scan path to memory, ready for triggering
function bResult = InitScanPath(handles)
global g_XPos g_YPos g_bShutterPos g_GalvoAO
bResult = 1;
nDev = get(handles.galvo_device, 'value');
if ~isfield(handles, 'tScanPath')
    uiwait(warndlg('No scan path has been loaded. Click on ''Load Path'' to load a new scan path to memory.'))
    bResult = 0;
    return
end
% Get scan path and shutter parameters
nFs = handles.tScanPath.nFs;
mXY_mm = handles.tScanPath.mXY; % mm

% Adjust by coordinates of the virtual path origin
sOrigBeam = get(handles.beam_origin, 'string');
vOrigBeam = eval(sOrigBeam);
sOrigPath = get(handles.path_origin, 'string');
vOrigPath = eval(sOrigPath);

cPixMM = get(handles.cal_pixels_mm, 'string');
nPixMM = str2num(cPixMM(1:end-6));
mXY = mXY_mm .* nPixMM; % pix

% Rotate stimulation matrix according to anatomical grid (if any)
mXY = TransformCoordsToReference(handles, mXY); % pix
vBregma = TransformCoordsToReference(handles, [0 0]);
vOrig = vOrigPath - vBregma;
mXY = mXY + [repmat(vOrig(1), size(mXY,1), 1) repmat(vOrig(2), size(mXY,1), 1)];

% Convert scan path from pixels to volts
mXY = ConvertScanPathFromPixelsToVolts(handles, mXY);

vShut = handles.tScanPath.vShut;
% Stop analog output if it is currently running
if isrunning(g_GalvoAO), stop(g_GalvoAO); end
% Set analog output sample rate
try
    set(g_GalvoAO, 'SampleRate', nFs); % Hz
catch
    uiwait(warndlg(sprintf('Sample rate of the trajectory (%.3f Hz) is either too high or too low.', nFs)))
    return;
end
% Generate analog XY galvo outputs for scan path
g_XPos = mXY(:,1);
g_YPos = mXY(:,2);
% Generate shutter open/close vector
g_bShutterPos = vShut;

% Re-load scan-path from script if bReload option is true
if handles.tScanPath.bReload
    LoadScanPathToMem(handles, handles.figure1, handles.tScanPath.sPath, handles.tScanPath.sFile, 1)
end

return

% --- Executes on button press in run_path.
function run_path_Callback(hObject, eventdata, handles)
global g_GalvoAO
% Release trigger button
set(handles.trigger_path, 'value', 0)
trigger_path_Callback(handles.trigger_path, eventdata, handles)
% Initialize scan path
if ~InitScanPath(handles), return, end
% Change TriggerType to immediate
set(g_GalvoAO, 'TriggerType', 'Immediate')
% Start analog output
StartAnalogOut(handles);
return

% --- Executes on button press in load_scan_script.
function load_scan_script_Callback(hObject, eventdata, handles)
% Load path scan script from disk
[sFile, sPath] = uigetfile('*.m', 'Pick an M-file');
if isequal(sFile,0) || isequal(sPath,0), return
else
    LoadScanPathToMem(handles, hObject, sPath, sFile, 0);
end
return

% --- Load scan path from script file
function LoadScanPathToMem(handles, hObject, sPath, sFile, bAutoLoad)
% If Shot mode is used, then create paths rather than load them
if strcmpi(sFile, 'Shot - Single.m')
    % Generate analog trace
    nPulseUp = str2num(get(handles.shutter_open_duration, 'string')); % pulse duration, ms
    [vShutInt, bResult] = GetBeamIntensity(handles);
    if ~bResult, return, end
    nShutInt = vShutInt(1);
    vShut = [ones(nPulseUp, 1) .* nShutInt; 0];
    mXY = zeros(length(vShut), 2);
    nFs = 1000;
    uiwait(warndlg('Current single-shot values were loaded into buffer and can now be triggered externally or Run manually.'))
elseif strcmpi(sFile, 'Shot - Train.m')
    % Generate analog trace from current pulse train parameters
    nPulseUp = str2num(get(handles.shutter_open_duration, 'string')); % ms
    nFreq = str2num(get(handles.shutter_frequency, 'string')); % Hz
    nPeriod = (1 / nFreq) * 1000; % ms
    nPulseDown = nPeriod - nPulseUp;
    [vShutInt, bResult] = GetBeamIntensity(handles);
    if ~bResult, return, end
    % Single pulse
    vOpen = [ones(nPulseUp, 1)];      % Open period
    vClosed = [zeros(nPulseDown, 1)]; % Close period (0V)
    nPulses = str2num(get(handles.shutter_pulses, 'string'));
    % Verify that we have as many voltage levels as we have pulses.
    if length(vShutInt) < nPulses
        vShutInt = repmat(vShutInt, 1, ceil(nPulses/length(vShutInt)));
    end
    % Compose pulse train
    vShut = [];
    for i = 1:nPulses
        vShut = [vShut; vOpen .* vShutInt(i); vClosed];
    end
    vShut = [vShut; 0];
    nFs = 1000;
    mXY = zeros(length(vShut), 2);
    uiwait(warndlg('Current pulse-train values were loaded into buffer and can now be triggered externally or Run manually.'))
else
    % Load template file
    cd(sPath);
    try
        if bAutoLoad
            eval(['[nFs, mXY, vShut, bReload] = ' sFile(1:end-2) '(1);']);
        else
            eval(['[nFs, mXY, vShut, bReload] = ' sFile(1:end-2) '(0);']);
        end
    catch
        uiwait(warndlg('An error occurred when loading the template file.'))
        return
    end

    % Check that all expected variables are created
    if ~exist('nFs') || ~exist('mXY') || ~exist('vShut') || ~exist('bReload') 
        uiwait(warndlg('Scan path script is not compatible (missing variable).'))
        return
    end
    
    % Check that galvo and shutter vectors have same length
    if length(vShut) ~= size(mXY, 1)
        uiwait(warndlg('Mirror and shutter vectors are not the same length.'))
        return
    end
end

% Compute and pop-up duration of the loaded trace. Indicate re-load only on
% command line if the bAutoLoad option is used.
if ~bAutoLoad
    sStr = sprintf('Scan-path loaded from script %s. Duration of scan path is %.1f s', ...
        sFile, length(vShut) / nFs);
    gs_disp(sStr);
    uiwait(msgbox(sStr, 'GS Path Duration', 'modal'))
else
    gs_disp(sprintf('Scan-path re-loaded from script %s. New duration of scan path is %.1f s', ...
        sFile, length(vShut) / nFs) )
end

% Create struct and insert into handles
tScanPath = struct([]);
tScanPath(1).nFs = nFs;
tScanPath(1).mXY = mXY; % mm
tScanPath(1).vShut = vShut;
tScanPath(1).bReload = bReload;
tScanPath(1).sPath = sPath;
tScanPath(1).sFile = sFile;
handles.tScanPath = tScanPath;
% Save new handles structure in GUI
guidata(hObject, handles)
% Redraw scan path on video frame
RedrawScanPath(handles)
return

% GS specific function for displaying text to command line. Overrided
% built-in disp function.
function gs_disp(sStr)
disp(sprintf('%s  %s', datestr(now, 'HH:mm:ss'), sStr))
return

% --- Executes on button press in calibrate.
function calibrate_Callback(hObject, eventdata, handles)
% Calibrate image (mm to pixels)
global g_XPos g_YPos g_bShutterPos g_nEnableButtonDownCallback
g_nEnableButtonDownCallback = 0; % disable ButtonDown callback
g_bShutterPos = 0; % make sure beam is off during calibration
grab_frame_Callback(hObject, eventdata, handles) % update frame

% STEP 1: Calibrate the value of pixels/mm by manually selecting N
% point-pairs in image with known distances.
% Collect n point-pairs
cPrompt = {'STEP 1: Use the mouse to select N number of paired-points. All paired-points should be equidistant at the value (mm) entered below. When done collecting points, press any key. The more points you select, the more accurate the calibration will be. To keep current calibration result, collect no points and press any key after closing this dialog. To skip this step, press Cancel.'};
cAnswer = inputdlg(cPrompt, 'Calibrate', 1, {'0.5'});
if ~isempty(cAnswer)
    nDist = str2double(cAnswer{1}); % mm
    if isempty(nDist), return; end
    vDist = []; % pixels
    set(handles.figure1, 'CurrentCharacter', '`');
    hold(handles.axes1, 'on')
    hPnt_a = []; hPnt_b = [];
    while strcmpi(get(handles.figure1, 'CurrentCharacter'), '`')
        [nX_a, nY_a] = ginput(1); % point A
        hPnt_a = plot(handles.axes1, nX_a, nY_a, 'r+');
        if ~strcmpi(get(handles.figure1, 'CurrentCharacter'), '`'), break, end
        [nX_b, nY_b] = ginput(1); % point B
        hPnt_b = plot(handles.axes1, nX_b, nY_b, 'r+');
        if ~strcmpi(get(handles.figure1, 'CurrentCharacter'), '`'), break, end
        delete([hPnt_a hPnt_b])
        vDist(end+1) = sqrt([nX_b - nX_a]^2 + [nY_b - nY_a]^2);
    end
    if ishandle(hPnt_a), delete(hPnt_a), end
    if ishandle(hPnt_b), delete(hPnt_b), end
    hold(handles.axes1, 'off')
    % Compute calibration
    if ~isempty(vDist)
        nCalVal = nanmean(vDist) / nDist;  % pixels/mm
        set(handles.cal_pixels_mm, 'string', sprintf('%.1f px/mm', nCalVal))
    else
        sCalVal = get(handles.cal_pixels_mm, 'string');
        nCalVal = str2double(sCalVal(1:end-6));
    end
else
    sCalVal = get(handles.cal_pixels_mm, 'string');
    nCalVal = str2double(sCalVal(1:end-6));
end

% STEP 2: Calibrate the value of V/mm by moving the beam automatically to N
% pre-set positions. User clicks on the current position until and aborts
% with a keypress if the beam moves out-of-frame
% NEW: Threshold image and find median location
nScale = 0.25; % how much to increase values in each iteration
mPos = [0 0;     % origin
        1 1;     % upper right
        1 -1;    % lower right
        -1 -1;   % lower left
        -1 1;    % upper left
];

% AUTOMATIC
uiwait(warndlg('STEP 2: The beam position will next be moved and tracked automatically. This will work best without any ambient illumination. Turn OFF all lights now if possible. Press any key to abort.', 'Calibration'))

% MANUAL
%uiwait(warndlg('STEP 2: Click on the current beam position as it changes position. Press any key when the beam moves out-of-frame.', 'Calibration'))

% Iterate over positions with an extending range until beam is out-of-frame
mClicks = [];
mBeamLocs = [];
set(handles.figure1, 'CurrentCharacter', '`');
bAbort = 0;
% Open shutter/turn on laser
set(handles.shutter_open_close, 'string', 'O', 'value', 1)
shutter_open_close_Callback(handles.shutter_open_close, eventdata, handles)
while strcmpi(get(handles.figure1, 'CurrentCharacter'), '`')
    % Iterate over positions and get user feedback
    for p = 1:size(mPos, 1)
        % New beam position
        g_XPos = mPos(p, 1) * nScale;
        g_YPos = mPos(p, 2) * nScale;
        % Min/max is -10/10 Volts
        if g_XPos > 10, g_XPos = 10; bAbort = 1; end
        if g_XPos < -10, g_XPos = -10; bAbort = 1;  end
        if g_YPos > 10, g_YPos = 10; bAbort = 1;  end
        if g_YPos < -10, g_YPos = -10; bAbort = 1;  end
        % Move beam
        StartAnalogOut(handles)
        % Grab frame
        grab_frame_Callback(hObject, eventdata, handles)
        
        % AUTOMATIC METHOD
        % Get current image
        hImg = findobj(handles.axes1, 'tag', 'GalvoVideoFrame');
        mCData = get(hImg, 'CData');
        mCData = double(mCData(:,:,1));
        nThresh = 80;
        % Threshold and find median location of beam pixels
        %mCData = (mCData - min(mCData(:))) ./ max(mCData(:));
        %mCData( mCData(:) < nThresh ) = 0; % threshold
        %mCData( mCData(:) >= nThresh ) = 1;
        [vI, vJ] = find(mCData >= nThresh);
        nX = median(vJ);
        nY = median(vI);
        if isnan(nX)
            set(handles.figure1, 'CurrentCharacter', 'q');
            break
        end
        
        % MANUAL METHOD
        %[nX, nY] = ginput(1); % User clicks on beam location

        % Abort if user pressed a key or max range is reached
        if ~strcmpi(get(handles.figure1, 'CurrentCharacter'), '`') || bAbort, break, end

        % Log all clicks and presented locations
        mClicks(end+1, 1:2) = [nX nY];
        mBeamLocs(end+1, 1:2) = [g_XPos g_YPos];
    end
    mPos = mPos + (mPos .* nScale); % extend range of beam
end
set(handles.shutter_open_close, 'string', 'X', 'value', 0)
shutter_open_close_Callback(handles.shutter_open_close, eventdata, handles)

if isempty(mClicks)
    uiwait(warndlg('Calibration aborted as no mouse clicks were recorded.'))
    return
end

% Compute volts/pix separately for X and Y galvo
% Any difference may be due to optics and/or galvonometer specs
% X galvo
vPixXOffsets = diff(mClicks(:,1)); % pixels
vMMXOffsets = vPixXOffsets / nCalVal; % mm
vVoltsXOffsets = diff(mBeamLocs(:,1)); % volts
%vVoltsMMX = nanmean(vVoltsXOffsets) ./ nanmean(vMMXOffsets);
% Linear fit of Volts/MM
nB = regress(vVoltsXOffsets, vMMXOffsets);
vVoltsMMX = nB;

% Y galvo
vPixYOffsets = diff(mClicks(:,2)); % pixels
vMMYOffsets = vPixYOffsets / nCalVal; % mm
vVoltsYOffsets = diff(mBeamLocs(:,2)); % volts
%vVoltsMMY = nanmean(vVoltsYOffsets) ./ nanmean(vMMYOffsets);
nB = regress(vVoltsYOffsets, vMMYOffsets);
vVoltsMMY = nB;

set(handles.cal_volts_mm_x, 'string', sprintf('%.3f', vVoltsMMX))
set(handles.cal_volts_mm_y, 'string', sprintf('%.3f', vVoltsMMY))

% Find origin
vIndx = find(mBeamLocs(:,1) == 0 & mBeamLocs(:,2) == 0);
nX = nanmedian(mClicks(vIndx,1));
nY = nanmedian(mClicks(vIndx,2));
set(handles.beam_origin, 'string', sprintf('[%.0f %.0f]', nX, nY))

% Enable ButtonDown callback on figure/axes
g_nEnableButtonDownCallback = 1;

% Confirm calibration is completed
uiwait(warndlg('Calibration successfully completed. Scalebar on frame indicates 1 mm.', 'Calibration'))

return

% --- Executes on button press in grab_frame.
function grab_frame_Callback(hObject, eventdata, handles)
global g_VideoObj g_nEnableButtonDownCallback g_bDumpFrames
persistent p_mFrameCircBuf p_nCircBufIndx % frame circular buffer
if isempty(p_nCircBufIndx), p_nCircBufIndx = 1; end
if isempty(g_bDumpFrames), g_bDumpFrames = 0; end
nCircBufSize = 10;

%try
    if isrunning(g_VideoObj)
        while get(g_VideoObj, 'FramesAvailable') < 1, pause(.1); end
        [mAllFrames, vTime] = getdata(g_VideoObj, g_VideoObj.FramesAvailable);
        if g_bDumpFrames, return, end % if we're pausing, make sure frames are dumped from buffer
        if strcmp(get(findobj(handles.figure1, 'tag', 'img_TemporalAveraging'), 'checked'), 'on')
            % Insert new frame into circular buffer
            p_mFrameCircBuf(:,:,:,p_nCircBufIndx) = mean(mAllFrames, 4);
            mFrame = uint8(mean(p_mFrameCircBuf, 4));
            p_nCircBufIndx = p_nCircBufIndx + 1;
            if p_nCircBufIndx > nCircBufSize, p_nCircBufIndx = 1; end % reset buffer index
        else
            mFrame = mAllFrames(:,:,:,end);
        end
    else
        if g_bDumpFrames, return, end
        p_nCircBufIndx = 1;
        p_mFrameCircBuf = [];
        start(g_VideoObj)
        % Wait until at least one frame is grabbed
        while get(g_VideoObj, 'FramesAvailable') < 2, pause(.1); end
        [mAllFrames, vTime] = getdata(g_VideoObj, g_VideoObj.FramesAvailable);
        mFrame = mAllFrames(:,:,:,end);
        stop(g_VideoObj)
        set(handles.view_video, 'Value', 0)
    end
    
    if strcmp(get(findobj(handles.figure1, 'tag', 'img_Colorize'), 'checked'), 'on') || ...
            strcmp(get(findobj(handles.figure1, 'tag', 'img_IntensityScaling'), 'checked'), 'on')
        mFrame = mFrame(:,:,1);
    end
    
    % Make sure image has 3 dimensions
    %if size(mFrame, 3) == 1
    %    mFrame(:,:,2) = mFrame(:,:,1);
    %    mFrame(:,:,3) = mFrame(:,:,1);
    %end
    
    
    for c = 1:size(mFrame, 3)
        mThisCh = mFrame(:,:,c);
        % Mask image
        tUserData = get(handles.axes1, 'UserData');
        if strcmp(get(findobj(handles.figure1, 'tag', 'img_ShowImageMask'), 'checked'), 'on')
            if isfield(tUserData, 'mMask')
                mThisCh(~tUserData.mMask) = min(mThisCh(:));
            end
        end
        mFrame(:,:,c) = mThisCh;

        % Sharpen image
        if strcmp(get(findobj(handles.figure1, 'tag', 'img_Sharpen'), 'checked'), 'on')
            H = fspecial('unsharp');
            mFrame(:,:,c) = imfilter(mFrame(:,:,c), H, 'replicate');
        end
    end
    
    % Push current video frame
    hImg = findobj(handles.axes1, 'tag', 'GalvoVideoFrame');
    if ishandle(hImg)
        if strcmp(get(findobj(handles.figure1, 'tag', 'img_IntensityScaling'), 'checked'), 'on')
            vCLim = [min(min(min(mFrame)))+2 max(max(max(mFrame)))-2];
            if ~any(isnan(vCLim))
                set(handles.axes1, 'CLim', sort(vCLim))
                set(hImg, 'CDataMapping', 'scaled')
                set(hImg, 'CData', mFrame)
            end
        else
            set(hImg, 'CDataMapping', 'direct')
            set(handles.axes1, 'CLimMode', 'auto')
            set(hImg, 'CData', mFrame)
        end
    else
        hImg = image(mFrame(:,:,1), 'Parent', handles.axes1, 'Tag', 'GalvoVideoFrame');
        set(handles.axes1, 'xtick', [], 'ytick', [], 'ydir', 'normal', 'visible', 'off')
        axis(handles.axes1, 'equal')
        set(hImg, 'uicontextmenu',  handles.img_ImgMenu, ... % Context menu
            'ButtonDownFcn', @(hObject,eventdata)axes1_ButtonDownFcn(hObject,eventdata,handles));
    end
    
    set(handles.axes1, 'xlim', [0 size(mFrame,2)], 'ylim', [0 size(mFrame,1)]);
    
    % Enable grid
    if strcmp(get(findobj(handles.figure1, 'tag', 'img_ShowGrid'), 'checked'), 'on')
        % Generate grid vectors
        nRes = 1; % grid resolution, mm     TODO: let it be user-define
        sPixMM = get(handles.cal_pixels_mm, 'string');
        nPixMM = str2num(sPixMM(1:end-6));
        vXTicks = (-20:nRes:20) .* nPixMM;
        vYTicks = (-20:nRes:20) .* nPixMM;
        
        % Generate grid matrix, in the format:
        % [ xs_1 ys_1
        %   xe_2 ye_2
        %    .    .
        %    .    .
        %   xs_n ys_n ]  for the n'th line and start (s) and end (e) points
        %
        nGrLim = 1000; % length of grid lines
        mXYPnts_o = zeros(length(vXTicks)*4,2).*NaN;
        mXYPnts_o(1:2:end/2, 1:2) = [vXTicks' repmat(-nGrLim,length(vXTicks),1)]; % x-grid line start
        mXYPnts_o(2:2:end/2, 1:2) = [vXTicks' repmat(nGrLim,length(vXTicks),1)];  % x-grid line end
        mXYPnts_o(end/2+1:2:end, 1:2) = [repmat(-nGrLim,length(vYTicks),1) vYTicks']; % y-grid line start
        mXYPnts_o(end/2+2:2:end, 1:2) = [repmat(nGrLim,length(vYTicks),1) vYTicks'];  % y-grid line end

        % Transform grid matrix to anatomical coordinate system
        mXYPnts = TransformCoordsToReference(handles, mXYPnts_o);

        % Plot grid matrix
        hGrid = findobj(handles.axes1, 'Tag', 'FrameGrid');
        vXData = [mXYPnts(1:2:end,1) mXYPnts(2:2:end,1)]';
        vYData = [mXYPnts(1:2:end,2) mXYPnts(2:2:end,2)]';
        if isempty(hGrid)
            hold(handles.axes1, 'on')
            hGrid = plot(handles.axes1, ...
                vXData, vYData, ...
                ':', 'linewidth', .5, ...
                'color', [.8 .8 .8], ... 
                'Tag', 'FrameGrid', ...
                'visible', 'on', ...
                'ButtonDownFcn', @(hObject,eventdata)axes1_ButtonDownFcn(hObject,eventdata,handles));
            % Bregma-line and mid-line should be thicker
            % Mid-line
            vXData_o = [mXYPnts_o(1:2:end,1) mXYPnts_o(2:2:end,1)]';
            nIndx = find(~vXData_o(1,:) & ~vXData_o(2,:));
            set(hGrid(nIndx), 'LineStyle', '-', 'LineWidth', 2, 'color', 'k')
            % Bregma
            vYData_o = [mXYPnts_o(1:2:end,2) mXYPnts_o(2:2:end,2)]';
            nIndx = find(~vYData_o(1,:) & ~vYData_o(2,:));
            set(hGrid(nIndx), 'LineStyle', '-', 'LineWidth', 2, 'color', 'k')
        else
            vXData_b = mat2cell(vXData',ones(1,length(vXData)), [2]);
            vYData_b = mat2cell(vYData',ones(1,length(vYData)), [2]);
            for i = 1:length(hGrid)
                set(hGrid(i), 'xdata', vXData_b{i}, 'ydata', vYData_b{i}, 'Tag', 'FrameGrid', 'visible', 'on');
            end
        end
    else
        hGrid = findobj(handles.axes1, 'Tag', 'FrameGrid');
        if ~isempty(hGrid)
            set(hGrid, 'visible', 'off');
        end
    end

    % Show/hide grid values
    if strcmp(get(findobj(handles.figure1, 'tag', 'img_ShowGridVals'), 'checked'), 'on')

        % Create a matrix with all [x y] label locations
        nRes = 1; % grid resolution, mm     TODO: let it be user-defined
        sPixMM = get(handles.cal_pixels_mm, 'string');
        nPixMM = str2num(sPixMM(1:end-6));
        vX_o = (-5:nRes:5)';
        vY_o = (-5:nRes:5)';
        mXY_o = [sort(repmat(vX_o(:),length(vY_o),1)),repmat(vY_o(:),length(vX_o),1)];
        mXY = mXY_o .* nPixMM;
        mXY = TransformCoordsToReference(handles, mXY);
        hold on

        hTxtLabels = findobj(handles.axes1, 'Tag', 'FrameLabelsTxt');
        if isempty(hTxtLabels)
            % Text labels
            for i = 1:size(mXY_o, 1)
                sLabels{i} = sprintf(' (%d,%d)', mXY_o(i,1), mXY_o(i,2));
            end
            hTxtLabels = text(mXY(:,1), mXY(:,2), sLabels(:), 'fontsize', 6, ...
                'color', [.761 .749 .647], 'Tag', 'FrameLabelsTxt');
        end
    end
    
    % Show/hide center box
    if isfield(tUserData, 'vCenterBox')
        vWH = tUserData(1).vCenterBox; % [width height] mm
        sCalVal = get(handles.cal_pixels_mm, 'string');
        nCalVal = str2num(sCalVal(1:end-6));
        vWH = vWH .* nCalVal; % pixels
        hCenterBox = findobj(handles.axes1, 'Tag', 'CenterBox');
        sOrigBeam = get(handles.beam_origin, 'string');
        vO = eval(sOrigBeam);
        vXY = [vO-vWH./2 vO+vWH./2]; % [x y x y]
        if isempty(hCenterBox)
            hold(handles.axes1, 'on')
            hCenterBox = plot(handles.axes1, vXY([1 1 3 3 1]), vXY([2 4 4 2 2]), 'r--');
            set(hCenterBox, 'Tag', 'CenterBox')
        else
            set(hCenterBox, 'xdata', vXY([1 1 3 3 1]), 'ydata', vXY([2 4 4 2 2]));
        end
        
    end
    
    % Update button callback
    if (~g_nEnableButtonDownCallback) && strcmp(get(handles.figure1,'visible'), 'on')
        set(hImg, 'ButtonDownFcn', '');
    else
        set(hImg, 'ButtonDownFcn', @(hObject,eventdata)axes1_ButtonDownFcn(hObject,eventdata,handles));
    end
    
    % Update timestamp
    hTimestamp = findobj(handles.axes1, 'Tag', 'FrameTimestamp');
    if isempty(hTimestamp)
        hTimestamp = text(5, 0, '', 'Parent', handles.axes1, 'color', 'w', ...
            'VerticalAlignment', 'bottom', 'fontsize', 8, 'Tag', 'FrameTimestamp');
    end
    set(hTimestamp, 'string', sprintf('%s (%.1f s)', datestr(now), vTime(end)))
    
    % Display calibration bar
    hCalBar = findobj(handles.figure1, 'tag', 'CalibrationBar');
    sCalVal = get(handles.cal_pixels_mm, 'string');
    nCalVal = str2num(sCalVal(1:end-6));
    if isempty(hCalBar)
        hold(handles.axes1, 'on')
        hCalBar = plot(handles.axes1, [size(mFrame, 2)-10 size(mFrame, 2)-10], [10 nCalVal], 'w-', 'linewidth', 5);
        hold(handles.axes1, 'off')
        set(hCalBar, 'tag', 'CalibrationBar')
    else
        set(hCalBar, 'xdata', [size(mFrame, 2)-10 size(mFrame, 2)-10], 'ydata', [10 nCalVal])
    end
%catch
%    uiwait(warndlg('A timeout occurred when grabbing a video snapshot. The camera may be configured to respond to a specific hardware trigger.'))
%end
RedrawScanPath(handles)
drawnow
return

% --- Executes on button press in shutter_open_close.
function shutter_open_close_Callback(hObject, eventdata, handles)
global g_bShutterPos g_XPos g_YPos
g_XPos = g_XPos(end);
g_YPos = g_YPos(end);
% Open/Close shutter
switch get(hObject, 'String')
    case 'X'
        % Close shutter
        set(hObject, 'String', 'O')
        g_bShutterPos = 0;
    case 'O'
        % Open shutter
        set(hObject, 'String', 'X')
        [g_bShutterPos, bResult] = GetBeamIntensity(handles);
        if ~bResult, return, end
        g_bShutterPos = g_bShutterPos(1);
end
% Start output
StartAnalogOut(handles)
set(handles.shutter_train, 'value', 0)
return

% --- Get beam intensity value(s) from GUI
function [vInt, bResult] = GetBeamIntensity(handles)
bResult = 1;
vInt = [];
try
    eval(get(handles.shutter_intensity, 'string'));
    vInt = ans;
    if isempty(vInt)
        uiwait(warndlg('Invalid beam intensity'));
        bResult = 0;
    end
catch
    uiwait(warndlg('Invalid beam intensity syntax'));
    bResult = 0;
end
% Truncate intensity to 0 - 5 V
vInt(find(vInt < 0)) = 0;
vInt(find(vInt > 5)) = 5;
return

% --- Executes on button press in shutter_single_pulse.
function shutter_single_pulse_Callback(hObject, eventdata, handles)
global g_XPos g_YPos g_GalvoAO g_bShutterPos
% Open shutter a single pulse
if isrunning(g_GalvoAO), stop(g_GalvoAO), end

% Pulse duration
nPulseUp = str2num(get(handles.shutter_open_duration, 'string')); % ms

% Set analog output sample rate to default of 1 kHz
if isrunning(g_GalvoAO), stop(g_GalvoAO), end
set(g_GalvoAO, 'SampleRate', 1000); % 1000 Hz

[vShutInt, bResult] = GetBeamIntensity(handles);
if ~bResult, return, end
vShutInt = vShutInt(1);

% Generate analog trace
g_bShutterPos = [ones(nPulseUp, 1) .* vShutInt; 0];
g_XPos = g_XPos(end);
g_YPos = g_YPos(end);

set(handles.shutter_train, 'value', 0)
set(handles.shutter_open_close, 'value', 0, 'string', 'O')

% Start single pulse
StartAnalogOut(handles)
return

% --- Executes on mouse press over axes background
function axes1_ButtonDownFcn(hObject, eventdata, handles)
% Abort if user pressed right or middle mouse buttons
if ~strcmpi(get(handles.figure1,'SelectionType'), 'normal')
    return
end
% Move beam to clicked position
global g_XPos g_YPos
% Clicked position
mCurrPnt = get(handles.axes1, 'CurrentPoint');
nX = mCurrPnt(1,1); % pix
nY = mCurrPnt(1,2); % pix
% Convert coordinate from pixels to V
vXY = ConvertScanPathFromPixelsToVolts(handles, [nX nY]);
g_XPos = vXY(1);
g_YPos = vXY(2);
% Move beam
StartAnalogOut(handles)
return


% --- Executes on button press in save_defaults.
function save_defaults_Callback(hObject, eventdata, handles)
% Get path
sPath = which('galvoscanner.m');
sPath = sPath(1:end-length('galvoscanner.m'));
sPath = [sPath 'DefaultGalvoSettings.mat'];
% Save default settings to disk
tDefs = struct([]);
tDefs(1).nDevice = get(handles.galvo_device, 'value');
tDefs(1).nXCh = get(handles.galvo_xchannel, 'value');
tDefs(1).nYCh = get(handles.galvo_ychannel, 'value');
tDefs(1).nTrgChIN = get(handles.galvo_trigger_in, 'value');
tDefs(1).nTrgChOUT = get(handles.galvo_trigger_out, 'value');
tDefs(1).nShutterCh = get(handles.shutter_channel, 'value');
tDefs(1).nCamera = get(handles.camera, 'value');
tDefs(1).nShutterDur = get(handles.shutter_open_duration, 'string');
tDefs(1).nShutterFreq = get(handles.shutter_frequency, 'string');
tDefs(1).nShutterPulses = get(handles.shutter_pulses, 'string');
tDefs(1).nShutterIntensity = get(handles.shutter_intensity, 'string');
tDefs(1).nGalvoIncrement = get(handles.galvo_increment, 'string');
tDefs(1).sBeamOrigin = get(handles.beam_origin, 'string');
tDefs(1).sPathOrigin = get(handles.path_origin, 'string');
tDefs(1).sCalPixelsMM = get(handles.cal_pixels_mm, 'string');
tDefs(1).sCalVoltsMMX = get(handles.cal_volts_mm_x, 'string');
tDefs(1).sCalVoltsMMY = get(handles.cal_volts_mm_y, 'string');
tDefs(1).sLogPath = get(handles.log_path, 'string');
tDefs(1).sAutoLogPath = get(handles.auto_logpath, 'value');
tDefs(1).tAnaCoords = get(handles.img_SetAnatomicalGrid, 'UserData');
tUserData = get(handles.axes1, 'UserData');
tDefs(1).mMask = tUserData.mMask;
save(sPath, 'tDefs');
return

% --- Executes on button press in load_defaults.
function load_defaults_Callback(hObject, eventdata, handles)
% Get path
sPath = which('galvoscanner.m');
sPath = sPath(1:end-length('galvoscanner.m'));
sPath = [sPath 'DefaultGalvoSettings.mat'];
% Load default settings from disk
load(sPath, 'tDefs');
% Assign
set(handles.galvo_device, 'value', tDefs(1).nDevice);
HardwareEnable(handles)
UpdateChannelLists(handles)
set(handles.galvo_xchannel, 'value', tDefs(1).nXCh);
set(handles.galvo_ychannel, 'value', tDefs(1).nYCh);
set(handles.galvo_trigger_in, 'value', tDefs(1).nTrgChIN);
set(handles.galvo_trigger_out, 'value', tDefs(1).nTrgChOUT);
set(handles.shutter_channel, 'value', tDefs(1).nShutterCh);
set(handles.camera, 'value', tDefs(1).nCamera);
set(handles.shutter_open_duration, 'string', tDefs(1).nShutterDur);
set(handles.shutter_frequency, 'string', tDefs(1).nShutterFreq);
set(handles.shutter_pulses, 'string', tDefs(1).nShutterPulses);
set(handles.shutter_intensity, 'string', tDefs(1).nShutterIntensity);
set(handles.galvo_increment, 'string', tDefs(1).nGalvoIncrement);
set(handles.beam_origin, 'string', tDefs(1).sBeamOrigin);
set(handles.path_origin, 'string', tDefs(1).sPathOrigin);
set(handles.cal_pixels_mm, 'string', tDefs(1).sCalPixelsMM);
set(handles.cal_volts_mm_x, 'string', tDefs(1).sCalVoltsMMX);
set(handles.cal_volts_mm_y, 'string', tDefs(1).sCalVoltsMMY);
set(handles.log_path, 'string', tDefs(1).sLogPath);
set(handles.auto_logpath, 'value', tDefs(1).sAutoLogPath);
set(handles.img_SetAnatomicalGrid, 'UserData', tDefs(1).tAnaCoords);
tUserData = get(handles.axes1, 'UserData');
tUserData.mMask = tDefs(1).mMask;
set(handles.axes1, 'UserData', tUserData);
ConfigureOutputChannels(handles)
return

% --- Executes on button press in lock_channel_edit.
function lock_channel_edit_Callback(hObject, eventdata, handles)
% When pressed down, channel configurations cannot be edited
switch get(hObject, 'value')
    case 0
        sEnable = 'on';
        set(hObject, 'string', 'Lock')
    case 1
        sEnable = 'off';
        set(hObject, 'string', 'Release')
end
set([handles.galvo_device ...
    handles.galvo_xchannel ...
    handles.galvo_ychannel ...
    handles.galvo_trigger_in ...
    handles.galvo_trigger_out ...
    handles.shutter_channel ...
    handles.camera ...
    handles.shutter_open_duration ...
    handles.shutter_frequency ...
    handles.shutter_pulses ...
    handles.shutter_intensity ...
    handles.galvo_increment ...
    handles.log_path ...
    handles.auto_logpath ...
    handles.select_logpath ...
    ] , 'enable', sEnable)
return


function shutter_open_duration_Callback(hObject, eventdata, handles)
% Check that input is >=1
if str2num(get(handles.shutter_open_duration, 'string')) < 1
    uiwait(warndlg('Minimum shutter opening duration is 1 ms.'))
    set(handles.shutter_open_duration, 'string', '1')
end
if str2num(get(handles.shutter_open_duration, 'string')) > 999
    uiwait(warndlg('Maximum shutter opening duration is 999 ms.'))
    set(handles.shutter_open_duration, 'string', '999')
end
return

% --- Executes during object creation, after setting all properties.
function shutter_open_duration_CreateFcn(hObject, eventdata, handles)
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end


% --- Executes on button press in stop_path.
function stop_path_Callback(hObject, eventdata, handles)
global g_GalvoAO g_XPos g_YPos g_bShutterPos
if isrunning(g_GalvoAO)
    % Get current output values
    nXPosI = get(g_GalvoAO, 'SamplesOutput');
    if nXPosI > 0
        try
            nXPos = g_XPos(get(g_GalvoAO, 'SamplesOutput'));
            nYPos = g_YPos(get(g_GalvoAO, 'SamplesOutput'));
        catch
            nXPos = g_XPos(1);
            nYPos = g_YPos(1);
        end
        g_XPos = nXPos;
        g_YPos = nYPos;
    end
    % Stop output engine
    if isrunning(g_GalvoAO), stop(g_GalvoAO); end
    % Close shutter
    g_bShutterPos = 0;
    StartAnalogOut(handles)
end
set(handles.shutter_open_close, 'value', 0, 'string', 'O')
% Release trigger button
set(handles.trigger_path, 'value', 0)
trigger_path_Callback(handles.trigger_path, eventdata, handles)
return


% --- Executes on selection change in camera.
function camera_Callback(hObject, eventdata, handles)
% Re-enable hardware
HardwareEnable(handles)    
% Update channel lists
UpdateChannelLists(handles)
return


% --- Executes during object creation, after setting all properties.
function camera_CreateFcn(hObject, eventdata, handles)
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end


% --- Executes on button press in view_video.
function view_video_Callback(hObject, eventdata, handles)
global g_VideoObj g_nEnableButtonDownCallback
switch get(handles.view_video, 'value')
    case 0 % 'View Video' button up
        % Stop timer and video
        oTimer = timerfind('Name', 'GalvoFrameGrabTimer');
        if ~isempty(oTimer)
            stop(oTimer);
            delete(oTimer);
        end
        pause(0.25)
        stop(g_VideoObj)
    case 1 % 'View Video' button down
        if ~isrunning(g_VideoObj)
            triggerconfig(g_VideoObj, 'immediate', 'none');
            set(g_VideoObj, 'TriggerRepeat', Inf, 'FramesPerTrigger', 1)
            start(g_VideoObj)
            % Frame grab timer
            oTimer = timerfind('Name', 'GalvoFrameGrabTimer');
            % Delete old timer
            if ~isempty(oTimer)
                stop(oTimer)
                delete(oTimer)
            end
            % Start new timer
            oTimer = timer('StartDelay', 0, 'TasksToExecute', Inf, 'Period', 1/20, ...
                'TimerFcn', @(hObject,eventdata)grab_frame_Callback(hObject,eventdata,handles), ...
                'Name', 'GalvoFrameGrabTimer', ...
                'StopFcn', @(hObject,eventdata)StopVideo(hObject,eventdata,handles), ...
                'ExecutionMode', 'fixedDelay');
            start(oTimer)
        end
end
g_nEnableButtonDownCallback = 1;
return


% --- Executes on button press in shutter_train.
function shutter_train_Callback(hObject, eventdata, handles)
global g_GalvoAO g_bShutterPos g_XPos g_YPos
switch get(handles.shutter_train, 'value')
    case 0 % togglebutton up
        g_bShutterPos = 0; % close shutter
        
    case 1 % togglebutton down
        % Program shutter pulse train

        % Pulse duration
        nPulseUp = str2num(get(handles.shutter_open_duration, 'string')); % ms
        nFreq = str2num(get(handles.shutter_frequency, 'string')); % Hz
        nPeriod = (1 / nFreq) * 1000; % ms
        nPulseDown = nPeriod - nPulseUp;
        
        % Set analog output sample rate to default of 1 kHz
        if isrunning(g_GalvoAO), stop(g_GalvoAO), end
        set(g_GalvoAO, 'SampleRate', 1000); % 1000 Hz

        [vShutInt, bResult] = GetBeamIntensity(handles);
        if ~bResult, return, end

        % Open period
        vOpen = ones(nPulseUp, 1);
        
        % Close period (0V)
        vClosed = [zeros(nPulseDown, 1)];
        
        % Create N pulses
        nPulses = str2num(get(handles.shutter_pulses, 'string'));

        % Verify that we have as many voltage levels as we have pulses.
        if length(vShutInt) < nPulses
            vShutInt = repmat(vShutInt, 1, ceil(nPulses/length(vShutInt)));
        end
       
        % Compose pulse train
        g_bShutterPos = [];
        for i = 1:nPulses
            g_bShutterPos = [g_bShutterPos; vOpen .* vShutInt(i); vClosed];
        end
        g_bShutterPos = [g_bShutterPos; 0];
        
        g_XPos = g_XPos(end);
        g_YPos = g_YPos(end);
        set(handles.shutter_open_close, 'value', 0, 'string', 'O')
        set(handles.shutter_train, 'value', 0)
end
% Start shutter pulse train
StartAnalogOut(handles)
return

% --- If Enable == 'on', executes on mouse press in 5 pixel border.
% --- Otherwise, executes on mouse press in 5 pixel border or over shutter_single_pulse.
function shutter_single_pulse_ButtonDownFcn(hObject, eventdata, handles)
return

function shutter_frequency_Callback(hObject, eventdata, handles)
return

% --- Executes during object creation, after setting all properties.
function shutter_frequency_CreateFcn(hObject, eventdata, handles)
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end

function galvo_increment_Callback(hObject, eventdata, handles)
return

% --- Executes during object creation, after setting all properties.
function galvo_increment_CreateFcn(hObject, eventdata, handles)
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end

% --- Move galvos to current set positions
function StartAnalogOut(handles)
global g_XPos g_YPos g_GalvoAO g_bShutterPos
if isempty(g_GalvoAO), return, end
g_GalvoAO.OutOfDataMode = 'Hold';
stop(g_GalvoAO) % stop analog output object
if isempty(g_XPos), g_XPos = 0; end
if isempty(g_YPos), g_YPos = 0; end

% Extend length of shutter vectors
if isempty(g_bShutterPos), g_bShutterPos = 0; end % close shutter by default
if length(g_bShutterPos) == 1 && length(g_XPos) > 1
    vShutterPos = repmat(g_bShutterPos, length(g_XPos), 1);
else vShutterPos = g_bShutterPos; end

% Extend length of galvo vectors
if length(g_XPos) == 1 && length(g_bShutterPos) > 1
    vXPos = repmat(g_XPos, length(g_bShutterPos), 1);
    vYPos = repmat(g_YPos, length(g_bShutterPos), 1);
else
    vXPos = g_XPos;
    vYPos = g_YPos;
end

% Limit XY voltages to [-10 10] V
vIndx = find(vXPos > 10); vXPos(vIndx) = 10;
vIndx = find(vXPos < -10); vXPos(vIndx) = -10;
vIndx = find(vYPos > 10); vYPos(vIndx) = 10;
vIndx = find(vYPos < -10); vYPos(vIndx) = -10;

% Limit shutter/intensity values to [0 5] V
vIndx = find(vShutterPos > 5); vShutterPos(vIndx) = 5;
vIndx = find(vShutterPos < 0); vShutterPos(vIndx) = 0;

nSamp = 50; % minimum number of samples required
if length(vXPos) < nSamp
    vXPos = [vXPos; repmat(vXPos(end), nSamp-length(vXPos), 1)];
    vYPos = [vYPos; repmat(vYPos(end), nSamp-length(vYPos), 1)];
    vShutterPos = [vShutterPos; repmat(vShutterPos(end), nSamp-length(vShutterPos), 1)];
end

% !!IMPORTANT HARDCODED PARAMETERS FOLLOW!!
% Depending on the orientation of the camera and mirrors, the beam's motion
% may be inverted relative to the intended motion. The following lines can
% be used to re-invert motion along the X and Y axis, respectively.
vXPos = vXPos .* -1;
vYPos = vYPos .* -1;

try
    putdata(g_GalvoAO, [vXPos vYPos vShutterPos]);
catch
    uiwait(warndlg(sprintf('A NIDAQ error occurred when queueing data.\n\n%s', lasterr)))
    return
end
try
    start(g_GalvoAO); % start analog output object
    set(handles.current_xpos, 'string', sprintf('X = %.1f V', g_XPos(end)));
    set(handles.current_ypos, 'string', sprintf('X = %.1f V', g_YPos(end)));
catch
    sStr = lasterr;
    nNL = sprintf('\n');
    sStr = strrep(sStr, nNL, ' ');
    sStr = strrep(sStr, '  ', ' ');
    uiwait(warndlg(sprintf('A NIDAQ error occurred when starting engine:\n\n%s', sStr)))
end
return

% --- Configure galvo channels on analog output object
function ConfigureOutputChannels(handles)
global g_GalvoAO g_bShutterPos
if isempty(g_GalvoAO), return, end
% Remove existing channels
stop(g_GalvoAO)
hChannels = g_GalvoAO.Channel;
delete(hChannels)
% Add currently selected channels
try
    addchannel(g_GalvoAO, get(handles.galvo_xchannel, 'value')-1);
    addchannel(g_GalvoAO, get(handles.galvo_ychannel, 'value')-1);
    addchannel(g_GalvoAO, get(handles.shutter_channel, 'value')-1);
    set(g_GalvoAO.Channel(1), 'OutputRange', [-10 10]);
    set(g_GalvoAO.Channel(2), 'OutputRange', [-10 10]);
    set(g_GalvoAO.Channel(3), 'OutputRange', [-10 10]);
catch
    uiwait(warndlg(sprintf('A NIDAQ error occurred when adding channels:\n\n%s', lasterr)))
    return
end
% Move galvos to their current positions and close the shutter
g_bShutterPos = 0;
StartAnalogOut(handles)
return

% --- Redraw scan path on video frame
function RedrawScanPath(handles)
hScanPath = findobj(handles.axes1, 'Tag', 'ScanPathLine');
hShutterPath = findobj(handles.axes1, 'Tag', 'ShutterPathLine');
hScanPathFirst = findobj(handles.axes1, 'Tag', 'ScanPathFirst');
if ~isfield(handles, 'tScanPath'), return, end
hold(handles.axes1, 'on')
mXY = handles.tScanPath.mXY; % mm, not calibrated for origin

% Convert scan path to pixels
%mXY = ConvertScanPathFromMMToPixels(handles, mXY);

% Adjust by coordinates of the virtual path origin
sOrigBeam = get(handles.beam_origin, 'string');
vOrigBeam = eval(sOrigBeam);
sOrigPath = get(handles.path_origin, 'string');
vOrigPath = eval(sOrigPath);
%vOrig = vOrigPath - vOrigBeam;
%mXY = mXY + [repmat(vOrig(1), size(mXY,1), 1) repmat(vOrig(2), size(mXY,1), 1)];

% Rotate stimulation matrix according to anatomical grid (if any)
mXY = handles.tScanPath.mXY; % mm
cPixMM = get(handles.cal_pixels_mm, 'string');
nPixMM = str2num(cPixMM(1:end-6));
mXY = mXY .* nPixMM; % pix
mXY = TransformCoordsToReference(handles, mXY);
vBregma = TransformCoordsToReference(handles, [0 0]);
vOrig = vOrigPath - vBregma;
mXY = mXY + [repmat(vOrig(1), size(mXY,1), 1) repmat(vOrig(2), size(mXY,1), 1)];

% Draw scan path
if ~get(handles.hide_path, 'value')
    if isempty(hScanPath)
        hScanPath = plot(mXY(:,1), mXY(:,2), 'w:', 'linewidth', .1, 'Parent', handles.axes1);
        set(hScanPath, 'Tag', 'ScanPathLine', 'Parent', handles.axes1, ...
            'ButtonDownFcn', @(hObject,eventdata)axes1_ButtonDownFcn(hObject,eventdata,handles))
    else
        set(hScanPath(1), 'xdata', mXY(:,1), 'ydata', mXY(:,2), 'Parent', handles.axes1)
    end
end

% Draw shutter open locations
if ~get(handles.hide_points, 'value')
    vShutIndx = find(handles.tScanPath.vShut);
    if isempty(hShutterPath)
        hShutterPath = plot(mXY(vShutIndx,1), mXY(vShutIndx,2), 'r.', 'Parent', handles.axes1, 'color', [.8 .5 0]);
        set(hShutterPath, 'Tag', 'ShutterPathLine', 'Parent', handles.axes1, ...
            'ButtonDownFcn', @(hObject,eventdata)axes1_ButtonDownFcn(hObject,eventdata,handles))
    else
        set(hShutterPath(1), 'xdata', mXY(vShutIndx,1), 'ydata', mXY(vShutIndx,2), 'Parent', handles.axes1)
    end

    % Mark starting point in blue
    if isempty(hScanPathFirst)
        hScanPathFirst = plot(mXY(1,1), mXY(1,2), 'g.', 'Parent', handles.axes1, 'tag', 'ScanPathFirst');
    else
        set(hScanPathFirst(1), 'xdata', mXY(1,1), 'ydata', mXY(1,2), 'Parent', handles.axes1)
    end

end
hold(handles.axes1, 'off')
return

% Convert path scan coordinates from mm to pixels
function mXY_pix = ConvertScanPathFromMMToPixels(handles, mXY)
% Calibrate from mm to pixels
cPixMM = get(handles.cal_pixels_mm, 'string');
nPixMM = str2num(cPixMM(1:end-6));
mXY_pix = mXY .* nPixMM;
% Shift by the location of the known origin
sOrig = get(handles.beam_origin, 'string');
vOrig = eval(sOrig);
%mXY_pix = mXY_pix + [repmat(vOrig(1), size(mXY_pix,1), 1) ...
%    repmat(vOrig(2), size(mXY_pix,1), 1)];
mXY_pix = mXY_pix + [repmat(vOrig(1), size(mXY_pix,1), 1) ...
    repmat(vOrig(2), size(mXY_pix,1), 1)];

return

% Convert volts to mm
function mXY_mm = ConvertScanPathFromVoltsToMM(handles, mXY)
cVoltsMM = get(handles.cal_volts_mm_x, 'string');
nVoltsMMX = str2num(cVoltsMM);
cVoltsMM = get(handles.cal_volts_mm_y, 'string');
nVoltsMMY = str2num(cVoltsMM);
mXY_mm(:,1) = mXY(:,1) ./ nVoltsMMX;
mXY_mm(:,2) = mXY(:,2) ./ nVoltsMMY;
return

% Convert path scan coordinates from volts to pixels
function mXY_pix = ConvertScanPathFromVoltsToPixels(handles, mXY)
% Volts to mm
mXY_mm = ConvertScanPathFromVoltsToMM(handles, mXY);
% mm to pixels
mXY_pix = ConvertScanPathFromMMToPixels(handles, mXY_mm);
return

% Convert path scan coordinates from pixels to volts
function mXY = ConvertScanPathFromPixelsToVolts(handles, mXY)
% Calibrate from pixels to volts
cPixMM = get(handles.cal_pixels_mm, 'string');
nPixMM = str2num(cPixMM(1:end-6));
% Shift by the location of the known origin
sOrig = get(handles.beam_origin, 'string');
vOrig = eval(sOrig);
mXY = [mXY(:,1) mXY(:,2)] - [repmat(vOrig(1), size(mXY,1), 1) repmat(vOrig(2), size(mXY,1), 1)];
%mXY(:,1) = -mXY(:,1);
% Convert from pixels to mm
mXY = mXY ./ nPixMM; % mm
% Convert from mm to volts
mXY = ConvertScanPathFromMMToVolts(handles, mXY);
% Limit XY voltages to [-10 10] V
mXY(mXY > 10) = 10;
mXY(mXY < -10) = -10;
return

% Convert path scan coordinates from mm to volts
function mXY = ConvertScanPathFromMMToVolts(handles, mXY)
% Calibrate from mm to pixels
cVoltsMM = get(handles.cal_volts_mm_x, 'string');
nVoltsMMX = str2num(cVoltsMM);
if isempty(nVoltsMMX)
    uiwait(errordlg('Image scaling has not been calibration. You must perform all calibration before proceeding.'))
    return
end
cVoltsMM = get(handles.cal_volts_mm_y, 'string');
nVoltsMMY = str2num(cVoltsMM);
mXY(:,1) = mXY(:,1) .* nVoltsMMX;
mXY(:,2) = mXY(:,2) .* nVoltsMMY;
return

% --- Executes on button press in set_path_origin.
function set_path_origin_Callback(hObject, eventdata, handles)
% Set virtual origin ([0 0]) location of scanned beam paths
axes(handles.axes1)
try [nX, nY] = ginput(1);
catch
    uiwait(warndlg('An error occurred in the ginput function. Turn off the real-time video feed and try again.'))
    return
end
set(handles.path_origin, 'string', sprintf('[%.0f %.0f]', nX, nY))
RedrawScanPath(handles)
return


function beam_origin_Callback(hObject, eventdata, handles)
return

% --- Executes during object creation, after setting all properties.
function beam_origin_CreateFcn(hObject, eventdata, handles)
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end


% --- Executes on button press in hide_path.
function hide_path_Callback(hObject, eventdata, handles)
hScanPath = findobj('Tag', 'ScanPathLine');
if get(handles.hide_path, 'value')
    delete(hScanPath)
end
RedrawScanPath(handles)
return


% --- Executes when application is closed.
function exit_app_Callback(hObject, eventdata, handles)
% Clean up after closing galvoscanner app
% Stop timers
oTimer = timerfind('Name', 'GalvoFrameGrabTimer');
if ~isempty(oTimer)
    stop(oTimer);
    delete(oTimer);
end
oTimer = timerfind('Name', 'GalvoDAQStatus');
if ~isempty(oTimer)
    stop(oTimer);
    delete(oTimer);
end
% Stop and delete DAQ board object
global g_GalvoAO
try
    stop(g_GalvoAO)
end
delete(g_GalvoAO)
% Stop and delete camera object
global g_VideoObj
if ~isempty(g_VideoObj) stop(g_VideoObj), end
delete(g_VideoObj)
% Clear global variables
clear global g_VideoObj
clear global g_GalvoAO
clear global g_XPos
clear global g_YPos
closereq
return

% --- Stop video object
function StopVideo(hObject,eventdata,handles)
global g_VideoObj
stop(g_VideoObj)
set(handles.view_video, 'Value', 0)
return

% --- Read and report current DAQ status
function ReadDAQStatus(hObject,eventdata,handles)
global g_GalvoAO g_XPos g_YPos g_bShutterPos
if isempty(g_GalvoAO), return; end
if isrunning(g_GalvoAO)
    nSamplesOut = get(g_GalvoAO, 'SamplesOutput');
    %if nSamplesOut == 0 || nSamplesOut > length(g_XPos), return, end    
    if nSamplesOut == 0, return, end
    set(handles.daq_indicator, 'backgroundcolor', [1 0 0])
    % Get current values from analog output object
    if nSamplesOut <= length(g_XPos)
        nXPos = g_XPos(nSamplesOut);
        nYPos = g_YPos(nSamplesOut);
    else
        nXPos = g_XPos(end);
        nYPos = g_YPos(end);
    end
    nShutterPos = g_bShutterPos(end);
    set(handles.current_xpos, 'string', sprintf('X = %.1f V', nXPos))
    set(handles.current_ypos, 'string', sprintf('X = %.1f V', nYPos))
    if nShutterPos
        set(handles.shutter_open_close, 'value', 1, 'string', 'X')
    else
        set(handles.shutter_open_close, 'value', 0, 'string', 'O')
    end
else
    % Reset indicator to green (i.e. no DAQ activity)
    set(handles.daq_indicator, 'backgroundcolor', [0 1 0])
    % Reset shutter pulse train togglebutton
    set(handles.shutter_train, 'value', 0)
end
% Update beam position in frame
sXPos = get(handles.current_xpos, 'string');
nXPos = str2num(sXPos(5:end-2));
sYPos = get(handles.current_ypos, 'string');
nYPos = str2num(sYPos(5:end-2));
UpdateBeamPositionDot(handles, nXPos, nYPos)

return

% --- Update beam position in frame
function UpdateBeamPositionDot(handles, nXPos, nYPos)

% Update beam target position in frame
hBeamPos = findobj(handles.axes1, 'Tag', 'BeamTargetPositionDot');
if isempty(hBeamPos)
    hold(handles.axes1, 'on')
    hBeamPos = plot(NaN, NaN, 'g+', 'parent', handles.axes1, ...
        'MarkerSize', 10, 'Tag', 'BeamTargetPositionDot', ...
        'ButtonDownFcn', @(hObject,eventdata)axes1_ButtonDownFcn(hObject,eventdata,handles));
    hold(handles.axes1, 'off')
end
% Volts to pixels
vXY = ConvertScanPathFromVoltsToPixels(handles, [nXPos nYPos]);
set(hBeamPos, 'xdata', vXY(1), 'ydata', vXY(2))
return

% --- Executes on selection change in galvo_trigger_out.
function galvo_trigger_out_Callback(hObject, eventdata, handles)
return

% --- Executes during object creation, after setting all properties.
function galvo_trigger_out_CreateFcn(hObject, eventdata, handles)
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end
return


% --- Verify shutter pulse input
function shutter_pulses_Callback(hObject, eventdata, handles)
if str2num(get(handles.shutter_pulses, 'string')) < 1
    uiwait(warndlg('Minimum number of shutter openings is 1.'))
    set(handles.shutter_pulses, 'string', '1')
end
if str2num(get(handles.shutter_pulses, 'string')) > 999
    uiwait(warndlg('Maximum number of shutter openings is 999.'))
    set(handles.shutter_pulses, 'string', '999')
end
return


% --- Executes during object creation, after setting all properties.
function shutter_pulses_CreateFcn(hObject, eventdata, handles)
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end



function cal_pixels_mm_Callback(hObject, eventdata, handles)
return

% --- Executes during object creation, after setting all properties.
function cal_pixels_mm_CreateFcn(hObject, eventdata, handles)
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end

function cal_volts_mm_x_Callback(hObject, eventdata, handles)

% --- Executes during object creation, after setting all properties.
function cal_volts_mm_x_CreateFcn(hObject, eventdata, handles)

function log_path_Callback(hObject, eventdata, handles)

% --- Executes during object creation, after setting all properties.
function log_path_CreateFcn(hObject, eventdata, handles)


% --- Executes on button press in select_logpath.
function select_logpath_Callback(hObject, eventdata, handles)
% Select log file directory
sCurrPath = get(handles.log_path, 'string');
if iscell(sCurrPath), sNewPath = uigetdir(sCurrPath{1});
else sNewPath = uigetdir(sCurrPath); end
set(handles.log_path, 'string', sNewPath)
return

% --- Logging function. Is called whenever analog output engine triggers
% TODO: Re-use log file across multiple calls until log path changes in GUI
%       or elsewhere
function LogAnalogOutput(hObject,eventdata,handles)
global g_GalvoAO g_XPos g_YPos g_bShutterPos
% Don't log if the beam is enabled manually
if length(g_XPos) == 1, return; end
% Get the log path. If the Auto button is pressed, get log path from
% currently running instance of DataRecorder
sCurrentFile = [];
if get(handles.auto_logpath, 'Value') == 1
    % Use path set in Data Recorder
    hDataRecApp = findobj(0, 'Tag', 'DataRecorder');
    if isempty(hDataRecApp)
        % If DataRecorder is not running in this Matlab instance,
        % get current path from the DataRecorder's own status file
        sPath = which('data_recorder');
        nIndx = findstr('data_recorder.m', sPath);
        sLoadPath = [sPath(1:nIndx-1) 'DataRecorder_CurrentHandles.mat'];
        if exist(sLoadPath) == 2
            DRHandles = load(sLoadPath, 'handles');
            sPath = DRHandles.handles.outputdir;
            sCurrentFile = [DRHandles.handles.currentfilename(1:end-4) '_GalvoScannerLog'];
        else
            uiwait(warndlg('Cannot retrieve logfile path, neither from a running instance of Data Recorder nor from its current status file. Aborting logging now.'))
            return
        end
    else
        % Data Recorder handles
        tDataRec_handles = guidata(hDataRecApp);
        sPath = get(tDataRec_handles.edit1, 'string');
        sCurrentFile = get(tDataRec_handles.edit12, 'string');
        sCurrentFile = [sCurrentFile(1:end-4) '_GalvoScannerLog'];
    end
    % Create path if it does not exist
    if ~exist(sPath, 'dir'), mkdir(sPath); end
else
    % Use manually set log path
    sPath = get(handles.log_path, 'string');
    sCurrentFile = 'GalvoScannerLog';
    if iscell(sPath), sPath = sPath{1}; end
end

% Initialize log file
vTs = get(g_GalvoAO,'InitialTriggerTime'); % 'sending' on-time
%sLogFile = sprintf('%s\\%s_%02.0f_%02.0f_%04.0f_%02.0f_%02.0f_%02.0f.txt', sPath, ...
%    sCurrentFile, ...
%    vTs(2), vTs(3), vTs(1), vTs(4), vTs(5), vTs(6));
sLogFile = sprintf('%s\\%s.txt', sPath, sCurrentFile);

% Check if log file already exists
bLogFileExisted = exist(sLogFile, 'file');
% Read headers of existing file
if bLogFileExisted
    hFID = fopen(sLogFile, 'r'); % open stream (append and create if necessary)
    % Time is on 3rd line
    for nL = 1:3, sTimeLine = fgetl(hFID); end
    for nL = 1:2, sHeaders = fgetl(hFID); end
    % Read all data content of log file
    mLog = [];
    while 1
        tLine = fgetl(hFID);
        if ~ischar(tLine), break, end
        mLog(end+1,:) = str2num(strrep(tLine, ',', ' '));
    end
    fclose(hFID);
    vTime = eval(['[' strrep(sTimeLine, ',', ' ') ']']);
    % Join milliseconds into seconds
    vTime = [vTime(1:5) vTime(6) + (vTime(7)/1000)];
    % Elapsed time since start of log file
    vET = vTs - vTime;
    nETSec = vET(6) + vET(5)*60 + vET(4)*60^2 + vET(3)*60^2*24; % elapsed time, sec
    % Compute total elapsed time in log file
    nETLogSec = sum(mLog(:,1)) / 1000; % sec
    % Total elapsed time since end of last write to log file
    nMSSinceLastWrite = (nETSec - nETLogSec) * 1000; % msec
else
    nMSSinceLastWrite = 0;
end

hFID = fopen(sLogFile, 'a'); % open stream (append and create if necessary)

% Header format:
% VALUES ARE LOGGED IN VOLTS
% galvoscanner
% YYYY,MM,DD,HH,MM,SS,MS
% MSEC_DIFF,X_VOLTS,Y_VOLTS,AP_POS,ML_POS,SHUTTER_VOLTS
% HEADER_END
% Write header info
%  - general information
if ~bLogFileExisted
    % Print header if log file did not exist from before:
    sStr = ['VALUES ARE LOGGED IN VOLTS AND MM\ngalvoscanner\n'];
    nCnt = fprintf(hFID, sStr);

    %  - file creation time
    sStr = sprintf('%d,%d,%d,%d,%d,%d,%02.2f\n', ...
        vTs(1),vTs(2),vTs(3),vTs(4),vTs(5),floor(vTs(6)),(vTs(6)-floor(vTs(6)))*1000);
    nCnt = fprintf(hFID, sStr);

    %  - column descriptions
    nCnt = fprintf(hFID, ['MSEC_DIFF,X_VOLTS,Y_VOLTS,AP_POS,ML_POS,SHUTTER_VOLTS\nHEADER_END\n']);
end

% Format new log lines
% dT\tX\tY\tAP\tML\tSH where,
%   T  is time since last sample
%   X  is X galvo voltage
%   Y  is Y galvo voltage
%   AP is the anterior-posterior beam location
%   ML is the medio-lateral beam location
%   S  is beam exposure (1=on, 0=off)
nFs = get(g_GalvoAO, 'SampleRate'); % Hz
nDT = (1/nFs) * 1000; % dT
% Convert voltages to beam position in anatomical coordinates (if available)
mXY_v = [g_XPos g_YPos];
mXY_pix = ConvertScanPathFromVoltsToPixels(handles, mXY_v);
sPxMM = get(handles.cal_pixels_mm, 'string'); % px/mm string
nPxMM = str2num(sPxMM(1:end-6)); % px/mm
mXY_pix_an = TransformCoordsToAnatomy(handles, mXY_pix);
mXY_mm_an = mXY_pix_an ./ nPxMM; % anatomical beam coordinates

% Find times when either of the analog outputs changed and log only these
vDTcum = cumsum(repmat(nDT, length(g_XPos), 1)'); % cumulative time
vData = [g_XPos g_YPos g_bShutterPos];
vLogIndx = logical([1;any(diff(vData),2)]);
sLines = sprintf('%d,%.3f,%.3f,%.3f,%.3f,%.3f\n', ...
    [[0+round(nMSSinceLastWrite) diff(vDTcum(vLogIndx))]; g_XPos(vLogIndx)'; g_YPos(vLogIndx)'; ...
    mXY_mm_an(vLogIndx,1)'; mXY_mm_an(vLogIndx,2)'; g_bShutterPos(vLogIndx)']);
nCnt = fprintf(hFID, sLines);
% Close log file
fclose(hFID)
return

% --- Executes on button press in auto_logpath.
function auto_logpath_Callback(hObject, eventdata, handles)
return


% --- Executes on button press in hide_points.
function hide_points_Callback(hObject, eventdata, handles)
hShutterPath = findobj(handles.axes1, 'Tag', 'ShutterPathLine');
hScanPathFirst = findobj(handles.axes1, 'Tag', 'ScanPathFirst');
if get(handles.hide_points, 'value')
    delete(hShutterPath)
    delete(hScanPathFirst)
end
RedrawScanPath(handles)
return


function shutter_intensity_Callback(hObject, eventdata, handles)
return

% --- Executes during object creation, after setting all properties.
function shutter_intensity_CreateFcn(hObject, eventdata, handles)
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end


% --- Executes on key press with focus on manual_right and no controls selected.
function manual_right_KeyPressFcn(hObject, eventdata, handles)
manual_right_Callback(hObject, eventdata, handles)
return

% --- Executes on key press with focus on manual_left and no controls selected.
function manual_left_KeyPressFcn(hObject, eventdata, handles)
manual_left_Callback(hObject, eventdata, handles)
return

% --- Executes on key press with focus on manual_up and no controls selected.
function manual_up_KeyPressFcn(hObject, eventdata, handles)
manual_up_Callback(hObject, eventdata, handles)
return

% --- Executes on key press with focus on manual_down and no controls selected.
function manual_down_KeyPressFcn(hObject, eventdata, handles)
manual_down_Callback(hObject, eventdata, handles)
return

function path_origin_Callback(hObject, eventdata, handles)
return

% --- Executes during object creation, after setting all properties.
function path_origin_CreateFcn(hObject, eventdata, handles)
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end


% --- Executes on selection change in select_preset_path.
function select_preset_path_Callback(hObject, eventdata, handles)
cFiles = get(hObject, 'string'); % get filename
nFile = get(hObject, 'value');
sFile = cFiles{nFile};
sMFilename = mfilename('fullpath'); % get path
sPath = [sMFilename(1:end-12) 'gs_templates'];
% load scan path
LoadScanPathToMem(handles, hObject, sPath, [sFile '.m'], 0)
return


% --- Executes during object creation, after setting all properties.
function select_preset_path_CreateFcn(hObject, eventdata, handles)
% Generate drop down list of pre-set paths
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end
sMFilename = mfilename('fullpath');
sPath = [sMFilename(1:end-12) 'gs_templates'];
sFiles = dir(sprintf('%s\\*.m', sPath)); % List of .m files
cTemplateFiles = {};
cTemplateFiles{1} = 'None';
if ~isempty(sFiles)
    for f = 1:size(sFiles, 1)
        cTemplateFiles{end+1} = sFiles(f).name(1:end-2);
    end
end
% Add default 'paths'
cTemplateFiles{end+1} = 'Shot - Single';
cTemplateFiles{end+1} = 'Shot - Train';
set(hObject, 'string', cTemplateFiles);
return


% --- Executes on button press in hide_video.
function hide_video_Callback(hObject, eventdata, handles)
global g_bDumpFrames
set(handles.figure1, 'units', 'pixels')
vPos = get(handles.figure1, 'position');
switch get(hObject, 'value')
    case 0
        set(handles.figure1, 'position', [vPos(1) vPos(2) 800 vPos(4)]);
        g_bDumpFrames = 0;
    case 1
        set(handles.figure1, 'position', [vPos(1) vPos(2) 360 vPos(4)]);
        g_bDumpFrames = 1;
end
return

% --------------------------------------------------------------------
function img_IntensityScaling_Callback(hObject, eventdata, handles)
switch get(hObject, 'checked')
    case 'off', set(hObject, 'checked', 'on')
    case 'on'
        set(hObject, 'checked', 'off')
        set(findobj('tag', 'img_Colorize'), 'checked', 'off')
        set(handles.figure1, 'colormap', gray)
end
grab_frame_Callback(hObject, eventdata, handles)
return

% --------------------------------------------------------------------
function img_Sharpen_Callback(hObject, eventdata, handles)
switch get(hObject, 'checked')
    case 'off', set(hObject, 'checked', 'on')
    case 'on', set(hObject, 'checked', 'off')
end
grab_frame_Callback(hObject, eventdata, handles)
return

% --------------------------------------------------------------------
function img_Save_Callback(hObject, eventdata, handles)
hImg = findobj(handles.axes1, 'tag', 'GalvoVideoFrame');
if isempty(hImg)
    return; % exit of image handle was not found
else
    % Grab frame without objects
    %mCData = get(hImg, 'CData');
    
    % Grab frame bitmap and everything on it
    mCData = getframe(handles.axes1);
    mCData = mCData.cdata;

    % Default path and filename
    sDefPath = [get(handles.log_path, 'string') '\'];
    vTs = clock;
    sDefFile = sprintf('galvoscanner_Frame_%02.0f_%02.0f_%04.0f_%02.0f_%02.0f_%02.0f.png', ...
        vTs(2), vTs(3), vTs(1), vTs(4), vTs(5), vTs(6));
    [sFile, sPath] = uiputfile({
        '*.png', 'Portable Network Graphics file (*.png)'; ...
        '*.eps', 'EPS file (*.eps)'; ...
        '*.jpg', 'JPEG image (*.jpg)'; ...
        '*.tif', 'TIFF image (*.tif)'; ...
        '*.bmp', 'Bitmap file (*.bmp)'; ...
        }, 'Save image as', [sDefPath sDefFile]);
    if sFile == 0, return, end % user cancelled
    
    if strcmpi(sFile(end-2:end), 'eps')
        % Grab frame and save as EPS
        hFigNew = figure;
        colormap gray
        hNewAx = copyobj(handles.axes1, hFigNew);
        set(hNewAx, 'units', 'pixels');
        vPos = get(hNewAx, 'position');
        set(hNewAx, 'units', 'pixels', 'position', [60 0 vPos(3:4)])
        saveas(hFigNew, [sPath sFile], 'eps')
        close(hFigNew)
    elseif strcmpi(sFile(end-2:end), 'jpg')
        imwrite(mCData, [sPath sFile], sFile(end-2:end), 'quality', 100)
    elseif strcmpi(sFile(end-2:end), 'png')
        imwrite(mCData, [sPath sFile], sFile(end-2:end), 'bitdepth', 8)
    else
        imwrite(mCData, [sPath sFile], sFile(end-2:end))
    end
end
return

% --------------------------------------------------------------------
function img_Colorize_Callback(hObject, eventdata, handles)
switch get(hObject, 'checked')
    case 'off'
        set(hObject, 'checked', 'on')
        set(findobj('tag', 'img_IntensityScaling'), 'checked', 'on')
        set(handles.figure1, 'colormap', jet)
    case 'on'
        set(hObject, 'checked', 'off')
        set(handles.figure1, 'colormap', gray)
end
grab_frame_Callback(hObject, eventdata, handles)
return

% --------------------------------------------------------------------
function img_ImgMenu_Callback(hObject, eventdata, handles)
return


% --------------------------------------------------------------------
function img_Measure_Callback(hObject, eventdata, handles)
% Measure distance on image

hold(handles.axes1, 'on')
[nX_a, nY_a] = ginput(1); % point A
hLine = findobj(handles.axes1, 'tag', 'MeasureLine');
if isempty(hLine)
    hLine = plot(handles.axes1, nX_a, nY_a, 'gx-');
else
    set(hLine, 'xdata', nX_a, 'ydata', nY_a)
end
[nX_b, nY_b] = ginput(1); % point B
set(hLine, 'xdata', [nX_a nX_b], 'ydata', [nY_a nY_b], 'tag', 'MeasureLine')
% Get real length of line (mm)
mXY = [nX_a nY_a; nX_b nY_b];
hText = findobj(handles.axes1, 'tag', 'MeasureLineText');
if isempty(hText)
    hText = text(mean(mXY(:,1)), mean(mXY(:,2)), '');
else
    set(hText, 'position', [mean(mXY, 1) 0])
end
mXY = ConvertScanPathFromPixelsToVolts(handles, mXY); % volts
mXY = ConvertScanPathFromVoltsToMM(handles, mXY); % mm
nLen = sqrt(diff(mXY(:,1))^2+diff(mXY(:,2))^2); % length, pix
set(hText, 'string', sprintf('%.0f um', nLen*1000), 'color', 'w', ...
    'backgroundcolor', 'none', 'tag', 'MeasureLineText', ...
    'horizontalalignment', 'center', 'fontsize', 7)

return

function cal_volts_mm_y_Callback(hObject, eventdata, handles)
return

% --- Executes during object creation, after setting all properties.
function cal_volts_mm_y_CreateFcn(hObject, eventdata, handles)
return

% --------------------------------------------------------------------
function img_ShowGrid_Callback(hObject, eventdata, handles)
switch get(hObject, 'checked')
    case 'off', set(hObject, 'checked', 'on')
    case 'on'
        set(hObject, 'checked', 'off')
        hTxtLabels = findobj(handles.axes1, 'Tag', 'FrameGrid');
        delete(hTxtLabels)
        return
end
grab_frame_Callback(hObject, eventdata, handles)
return

% --------------------------------------------------------------------
function img_SetAnatomicalGrid_Callback(hObject, eventdata, handles)
% Click on 2 known reference points
uiwait(warndlg('Click on two locations with known anterior/posterior and medial/lateral coordinates. It is assumed that locations right and posterior to bregma are negative.'))

[vX, vY] = ginput(2);
hold(handles.axes1, 'on');
hAnaDots = plot(vX, vY, 'c+');
hAnaTxt = text(vX, vY, {'  1', '  2'}, 'color', 'w');

% Get anatomical coordinates
cAns = inputdlg({'1 - Anterior/Posterior' '1 - Medial/Lateral' ...
    '2 - Anterior/Posterior' '2 - Medial/Lateral'}, 'Coordinates', 1);
delete([hAnaDots; hAnaTxt])
if isempty(cAns), return, end

% Check that supplied coordinates are ok
vAP = [str2num(cAns{1}) str2num(cAns{3})];
vML = [str2num(cAns{2}) str2num(cAns{4})];
if length(vAP) < 2 || length(vML) < 2
    errordlg('You must enter a value in each field.')
    return
end
if diff(vAP) == 0 || diff(vML) == 0
    errordlg('The two clicked points must be unique along each axis.')
    return
end

% Store coordinates
tAnaCoords = struct();
tAnaCoords(1).vAP = vAP;
tAnaCoords(1).vX  = [vX];
tAnaCoords(1).vML = vML;
tAnaCoords(1).vY  = [vY];
set(hObject, 'UserData', tAnaCoords)

hTxtLabels = findobj(handles.axes1, 'Tag', 'FrameGrid');
delete(hTxtLabels)
hTxtLabels = findobj(handles.axes1, 'Tag', 'FrameLabelsTxt');
delete(hTxtLabels)

% Delete temporary objects and refresh video
grab_frame_Callback(hObject, eventdata, handles)
return

% --------------------------------------------------------------------
function img_ShowGridVals_Callback(hObject, eventdata, handles)
switch get(hObject, 'checked')
    case 'off', set(hObject, 'checked', 'on')
    case 'on'
        set(hObject, 'checked', 'off')
        hTxtLabels = findobj(handles.axes1, 'Tag', 'FrameLabelsTxt');
        delete(hTxtLabels)
        return
end
grab_frame_Callback(hObject, eventdata, handles)
return

% --------------------------------------------------------------------
% Transform [x y] anatomical coordinates to axis coordinates
function mXY_out = TransformCoordsToReference(handles, mXY_in)
% mXY_in should be in mm
if ~isempty(get(handles.img_SetAnatomicalGrid, 'UserData'))
    tAnaCoords = get(handles.img_SetAnatomicalGrid, 'UserData');

    % Clicked coordinates
    vX = tAnaCoords.vX; vY = tAnaCoords.vY;

    % Corresponding anatomical coordinates
    vAP = tAnaCoords.vAP; vML = tAnaCoords.vML;

    % Angle to rotate grid by
    nA = atan2(diff(vY), diff(vX)); % rotation
    nA_r = atan2(diff(vML), diff(vAP)); % rotation

    % Find origin in default grid (as we need to rotate around this point)
    % i) Rotate user coordinates so that AP/ML axes are at straight angles
    nT = -(nA_r - nA);
    vX_r2 = ( vX .* cos(nT)) + (vY .* sin(nT));
    vY_r2 = (-vX .* sin(nT)) + (vY .* cos(nT));
    % ii) Get calibration scale from user points
    nXPixMM = diff(vX_r2) / diff(vAP); % pix/mm
    nYPixMM = diff(vY_r2) / diff(vML); % pix/mm
    nPixMM = (nXPixMM + nYPixMM) / 2;
    
    % iii) Find origin (average of both points, for accuracy)
    vO(1,1) = vX_r2(1) - (vAP(1) * nPixMM);
    vO(1,2) = vY_r2(1) - (vML(1) * nPixMM);
    vO(2,1) = vX_r2(2) - (vAP(2) * nPixMM);
    vO(2,2) = vY_r2(2) - (vML(2) * nPixMM);
    vO = mean(vO, 1); % origin in default grid
    
    % iv) Rotate origin back to default grid (reverse of above)
    vO_x = ( vO(1) .* cos(nT)) - (vO(2) .* sin(nT));
    vO_y = ( vO(1) .* sin(nT)) + (vO(2) .* cos(nT));
    vO = [vO_x vO_y]; % origin in user-generated grid
    
    % 1) Translate user-generated grid so that origin is at [0 0]
    mXY_out(:,1) = mXY_in(:,1) - vO(1);
    mXY_out(:,2) = mXY_in(:,2) - vO(2);
    
    % 2) Rotate grid matrix
    nTheta = (nA_r - nA); % radians
    vXPnts_rot = ( mXY_in(:,1) .* cos(nTheta)) + (mXY_in(:,2) .* sin(nTheta));
    vYPnts_rot = (-mXY_in(:,1) .* sin(nTheta)) + (mXY_in(:,2) .* cos(nTheta));
    mXY_out(:,1) = vXPnts_rot;
    mXY_out(:,2) = vYPnts_rot;

    % 3) Translate user-generated grid back
    mXY_out(:,1) = mXY_out(:,1) + vO(1);
    mXY_out(:,2) = mXY_out(:,2) + vO(2);
else mXY_out = mXY_in; end
return

% --------------------------------------------------------------------
% Transform [x y] axis coordinates to anatomical coordinates
function mXY_out = TransformCoordsToAnatomy(handles, mXY_in)
%mXY_out = mXY_in;
%return
if ~isempty(get(handles.img_SetAnatomicalGrid, 'UserData'))
    tAnaCoords = get(handles.img_SetAnatomicalGrid, 'UserData');

    % Clicked coordinates
    vX = tAnaCoords.vX; vY = tAnaCoords.vY;

    % Corresponding anatomical coordinates
    vAP = tAnaCoords.vAP; vML = tAnaCoords.vML;

    % Angle to rotate grid by
    nA = atan2(diff(vY), diff(vX)); % rotation
    nA_r = atan2(diff(vML), diff(vAP)); % rotation

    % Find origin in default grid (as we need to rotate around this point)
    % i) Rotate user coordinates so that AP/ML axes are at straight angles
    nT = -(nA_r - nA);
    vX_r2 = ( vX .* cos(nT)) + (vY .* sin(nT));
    vY_r2 = (-vX .* sin(nT)) + (vY .* cos(nT));
    % ii) Get calibration scale from user points
    nXPixMM = diff(vX_r2) / diff(vAP); % pix/mm
    nYPixMM = diff(vY_r2) / diff(vML); % pix/mm
    nPixMM = (nXPixMM + nYPixMM) / 2;
    
    % iii) Find origin (average of both points, for accuracy)
    vO(1,1) = vX_r2(1) - (vAP(1) * nPixMM);
    vO(1,2) = vY_r2(1) - (vML(1) * nPixMM);
    vO(2,1) = vX_r2(2) - (vAP(2) * nPixMM);
    vO(2,2) = vY_r2(2) - (vML(2) * nPixMM);
    vO = mean(vO, 1); % origin in default grid
    
    % iv) Rotate origin back to default grid (reverse of above)
    vO_x = ( vO(1) .* cos(nT)) - (vO(2) .* sin(nT));
    vO_y = ( vO(1) .* sin(nT)) + (vO(2) .* cos(nT));
    vO = [vO_x vO_y]; % origin in user-generated grid
    
    % 1) Translate input points in axis coordinates so origin is at [0 0]
    mXY_out(:,1) = mXY_in(:,1) - vO(1);
    mXY_out(:,2) = mXY_in(:,2) - vO(2);
    
    % 2) Rotate grid matrix
    nTheta = -(nA_r - nA); % radians
    vXPnts_rot = ( mXY_out(:,1) .* cos(nTheta)) + (mXY_out(:,2) .* sin(nTheta));
    vYPnts_rot = (-mXY_out(:,1) .* sin(nTheta)) + (mXY_out(:,2) .* cos(nTheta));
    mXY_out(:,1) = vXPnts_rot;
    mXY_out(:,2) = vYPnts_rot;
else mXY_out = mXY_in; end
return


% --- Executes on mouse motion over figure - except title and menu.
function figure1_WindowButtonMotionFcn(hObject, eventdata, handles)
% Update cursor position data
mXY = get(handles.axes1, 'CurrentPoint'); % pixel coordinates
mXY = [mXY(1,1) mXY(1,2)];% ./ handles.cal_pixels_mm
mXY_out = TransformCoordsToAnatomy(handles, mXY);
sPxMM = get(handles.cal_pixels_mm, 'string'); % px/mm string
nPxMM = str2num(sPxMM(1:end-6)); % px/mm
mXY_out = mXY_out ./ nPxMM;
hCursorText = findobj(handles.axes1, 'Tag', 'CursorText');
if isempty(hCursorText)
    vImgSize = get(handles.axes1, 'CameraTarget');
    hCursorText = text(vImgSize(2), 0, '', 'Parent', handles.axes1, 'color', 'w', ...
            'VerticalAlignment', 'bottom', 'fontsize', 8, 'Tag', 'CursorText', ...
            'HorizontalAlignment', 'left' );
else
    sStr = sprintf('AP: %.2f   ML: %.2f', mXY_out(1), mXY_out(2));
    set(hCursorText, 'string', sStr, 'color', 'g');
end
    
return


% --- Estimate and save the beam intensity map
function img_BeamAnalysis_Callback(hObject, eventdata, handles)
% This function moves the beam within the entire allowed range for the
% galvos. For each position, the projected beam intensity is measured. All
% measured intensities are then used to estimate an intensity map, whose
% function is to provide an overview of uneven beam intensities across the
% scanned surfaces.
global g_XPos g_YPos g_bDumpFrames
persistent p_nRes_mm p_nSpotSize p_nReps
if isempty(p_nRes_mm), p_nRes_mm = 0.5; end
if isempty(p_nSpotSize), p_nSpotSize = 0.05; end
if isempty(p_nReps), p_nReps = 1; end

% Get parameters
sMsg = 'This simple beam diagnostics module allows you to estimate beam size, shape, relative power and positional from the projected beam profile viewed by a connected camera. As such, beam estimates are prone to additional error introduced by the camera optics. You should also take into consideration the reflective properties of the material used to project the beam into the camera (e.g. piece of paper, mirror etc).';
cAns = inputdlg({sprintf('%s\n\nScan resolution (mm)', sMsg) 'Approximate spotsize (mm)', 'Averaging (frames)'}, ...
    'Beam Diagnostics', 1, {num2str(p_nRes_mm) num2str(p_nSpotSize) num2str(p_nReps)});
if isempty(cAns), return, end
p_nRes_mm = str2num(cAns{1});
p_nSpotSize = str2num(cAns{2});
p_nReps = str2num(cAns{3});

% Start video
set(handles.view_video, 'value', 1)
view_video_Callback(hObject, eventdata, handles)

% Parameters
nXRes_mm = p_nRes_mm; % mm
nYRes_mm = p_nRes_mm; % mm
nLim_mm = 3; % mm

% Generate scan points (volts)
cVoltsMM = get(handles.cal_volts_mm_x, 'string');
nVoltsMMX = str2num(cVoltsMM);
cVoltsMM = get(handles.cal_volts_mm_y, 'string');
nVoltsMMY = str2num(cVoltsMM);
vXPos_mm = -nLim_mm:nXRes_mm:nLim_mm;
vYPos_mm = -nLim_mm:nYRes_mm:nLim_mm;
vXPos = vXPos_mm * nVoltsMMX;
vYPos = vYPos_mm * nVoltsMMY;
cPixMM = get(handles.cal_pixels_mm, 'string');
nPixMM = str2num(cPixMM(1:end-6));

% Initialize vectors
mSize = zeros(length(vXPos), length(vYPos)).*NaN;
mIntensity = zeros(length(vXPos), length(vYPos)).*NaN;
cBeamPixels = cell(length(vXPos), length(vYPos));
cBeamPosition = cell(length(vXPos), length(vYPos));
mBeamTargetPos = cell(length(vXPos), length(vYPos));

% Get a background image with the galvos all the way out
g_XPos = 9.9; g_YPos = 9.9;
StartAnalogOut(handles) % Move beam
pause(.2) % pause to make sure galvos moves
hImg = findobj(handles.axes1, 'tag', 'GalvoVideoFrame');
mCData = get(hImg, 'CData');
mCDataBG = double(mCData(:,:,1));

% Iterate over positions and get user feedback
set(handles.figure1, 'CurrentCharacter', '`');
for xi = 1:length(vXPos)
    g_XPos = vXPos(xi);
    for yi = 1:length(vYPos)
        g_YPos = vYPos(yi);
        StartAnalogOut(handles) % Move beam
        pause(.2) % pause to make sure galvos moves

        g_bDumpFrames = 1; % Freeze frame capture
        % Get current image
        hImg = findobj(handles.axes1, 'tag', 'GalvoVideoFrame');
        mCData = get(hImg, 'CData');
        g_bDumpFrames = 0; % Restart frame capture
        mCData = double(mCData(:,:,1)) - mCDataBG;

        % Average images to reduce pixel noise
        for ri = p_nReps-1
            pause(.1)
            mCData2 = get(hImg, 'CData');
            mCData2 = double(mCData2(:,:,1)) - mCDataBG;
            mCData = (mCData+mCData2)./2;
        end

        % Find spot center
        nThresh = 100;%median(mCData(:)) + 4*std(mCData(:));
        [vI, vJ] = find(mCData >= nThresh);
        nX = round(median(vJ)); nY = round(median(vI));

        % Get beam statistics
        if isnan(nX)
            mSize(xi,yi) = NaN;
            mIntensity(xi,yi) = NaN;
            cBeamTargetPos{xi,yi} = [NaN NaN];
            cBeamPosition{xi,yi} = [NaN NaN];
        else
            mCData_lastused = mCData;
            % Get ROI 2 x spotsize centered on spot location
            nR = round(p_nSpotSize * 2 * nPixMM);
            vIr = (nY-nR):(nY+nR);
            vJr = (nX-nR):(nX+nR);
            mCDataThis = padarray(mCData, [nR nR], 'circular', 'both');
            mSpotImg = mCDataThis(vIr+nR, vJr+nR);
            % 1) Projected beam size
            nThresh = median(mSpotImg(:)) + (2*std(mSpotImg(:)));
            [vIs, vJs] = find(mSpotImg > nThresh);
            mSize(xi,yi) = length(vIs); % pixels^2

            % 3) Projected mean beam intensity
            vInt = mSpotImg( sub2ind(size(mSpotImg), vIs, vJs) );
            mIntensity(xi,yi) = sum(vInt); % net power, i.e. sum of all pixels
            % 4) Beam asymmetry
            % TODO
            % image(mSpotImg); plot(vJs,vIs,'k.')
            % 5) Spatial offset (from true and command position)
            hTruePos = findobj(handles.axes1, 'tag', 'BeamTargetPositionDot');
            vXY_tp = [get(hTruePos, 'xdata') get(hTruePos, 'ydata')];
            if isempty(vXY_tp), vXY_tp = [NaN NaN]; end
            cBeamTargetPos{xi,yi} = vXY_tp;
            % 6) Save beam pixels
            cBeamPixels{xi,yi} = [vJs+nX-nR-1 vIs+nY-nR-1];
            % 7) Beam position
            vXY_tp = mean([vJs+nX-nR-1 vIs+nY-nR-1], 1);
            if isempty(vXY_tp), vXY_tp = [NaN NaN]; end
            cBeamPosition{xi,yi} = vXY_tp;
        end

        % Abort if user pressed a key
        if ~strcmpi(get(handles.figure1, 'CurrentCharacter'), '`')
            g_bDumpFrames = 0;
            keyboard
        end
    end
end

% Stop video
set(handles.view_video, 'value', 0)
view_video_Callback(hObject, eventdata, handles)

% Plot beam diameter
hFig = figure; hAx = subplot(1,1,1);
set(hFig, 'name', 'GalvoScanner Beam Diagnostics - Diameter', 'numberTitle', 'off')
mDiam = sqrt( 4.*mSize ./ pi ) ./ nPixMM; % diameter (mm), assuming a gaussian beam
surf(nanmean(mDiam, 3)'); shading flat; view(2)
title('Beam Area (mm)')
set(hAx, 'xtick', 1:length(vXPos), 'xticklabel', vXPos_mm, ...
    'ytick', 1:length(vYPos), 'yticklabel', vYPos_mm);
xlabel('X (mm)'); ylabel('Y (mm)')
axis tight equal; box on; colorbar
colormap hot

% Plot beam power
hFig = figure; hAx = subplot(1,1,1);
set(hFig, 'name', 'GalvoScanner Beam Diagnostics - Power', 'numberTitle', 'off')
mIntensity = mIntensity ./ mSize;
mIntensity = (mIntensity ./ max(max(mIntensity))) .* 100;
surf(mean(mIntensity, 3)'); shading flat; view(2)
title('Net Beam Intensity / Beam Area (% of max)')
set(hAx, 'xtick', 1:length(vXPos), 'xticklabel', vXPos_mm, ...
    'ytick', 1:length(vYPos), 'yticklabel', vYPos_mm);
xlabel('X (mm)'); ylabel('Y (mm)')
axis tight equal; box on; colorbar
colormap hot

% Plot beam shape
hFig = figure; hAx = subplot(1,1,1); hold on
set(hFig, 'name', 'GalvoScanner Beam Diagnostics - Shape', 'numberTitle', 'off')
for i = 1:length(cBeamPixels(:))
    cXY = cBeamPixels(i);
    vXY = cXY{1};
    if ~isempty(vXY)
        [vK] = convhull(vXY(:,1), vXY(:,2));
        plot(vXY(:,1), vXY(:,2), '.', 'color', [.4 .4 .4])
        plot(vXY(vK,1), vXY(vK,2), 'k-')
    end
end
title('Beam Shape')
xlabel('X (mm)'); ylabel('Y (mm)')
vOrigin = eval(get(handles.beam_origin,'string'));
vLim = size(mCData_lastused);

vTick_mm = -5:1:5;
vXTick_pix = (vTick_mm .* nPixMM) + vOrigin(1);
vYTick_pix = (vTick_mm .* nPixMM) + vOrigin(2);
set(hAx, 'xlim', [0 vLim(2)], 'ylim', [0 vLim(1)], ...
    'xtick', vXTick_pix, 'ytick', vYTick_pix, ...
    'xticklabel', vTick_mm, 'yticklabel', vTick_mm)
grid on; axis equal; box on

% Plot beam position
hFig = figure; hAx = subplot(1,1,1); hold on
set(hFig, 'name', 'GalvoScanner Beam Diagnostics - Spatial Offset', 'numberTitle', 'off')
for i = 1:length(cBeamPosition(:))
    cXY = cBeamPosition(i);
    cXY_t = cBeamTargetPos(i);
    vXY = cXY{1}; vXY_t = cXY_t{1};
    if isempty(vXY) || isempty(vXY_t), continue, end    
    vXY = (vXY - vOrigin) ./ nPixMM;
    vXY_t = (vXY_t - vOrigin) ./ nPixMM;
    if ~isempty(vXY)
        hD1 = plot(vXY(1), vXY(2), 'k.', 'markersize', 14);
        hD2 = plot(vXY_t(1), vXY_t(2), 'r.', 'markersize', 10);
        hL1 = plot([vXY(1) vXY_t(1)], [vXY(2) vXY_t(2)], 'r-');
        nO = sqrt(sum((vXY - vXY_t).^2)); % offset
        set([hD1 hD2 hL1], 'ButtonDownFcn', ['msgbox(''Offset = ' num2str(nO) ' mm'')']);
    end
end
title('Beam Position (black) and Target (red)')
set(hAx, 'xtick', vXPos_mm, 'ytick', vYPos_mm, ...
    'xlim', [min(vXPos_mm) max(vXPos_mm)], ...
    'ylim', [min(vYPos_mm) max(vYPos_mm)] );
xlabel('X (pix)'); ylabel('Y (pix)')
grid on; axis equal; box on
return

% --- Set image mask
function img_SetImageMask_Callback(hObject, eventdata, handles)
axes(handles.axes1)
mMask = roipoly;
tUserData = get(handles.axes1, 'userData');
tUserData(1).mMask = mMask;
set(handles.axes1, 'userData', tUserData)
set(findobj(handles.figure1, 'tag', 'img_ShowImageMask'), 'checked', 'on')
grab_frame_Callback(hObject, eventdata, handles)
return

% --- Remove image mask
function img_ShowImageMask_Callback(hObject, eventdata, handles)
axes(handles.axes1)
tUserData = get(handles.axes1, 'userData');
switch get(hObject, 'checked')
    case 'off'
        set(hObject, 'checked', 'on')
    case 'on'
        set(hObject, 'checked', 'off')
end
grab_frame_Callback(hObject, eventdata, handles)
return

% --- Enable/disable temporal frame averaging
function img_TemporalAveraging_Callback(hObject, eventdata, handles)
switch get(hObject, 'checked')
    case 'off', set(hObject, 'checked', 'on')
    case 'on', set(hObject, 'checked', 'off')
end
return

% --------------------------------------------------------------------
function img_DrawCenterBox_Callback(hObject, eventdata, handles)
% Draw a centering box with custom height and width. The purpose of this
% box is to make sure the microscope/camera is centered properly over the
% region of interest during positioning.
persistent p_nHeight p_nWidth;
if isempty(p_nHeight), p_nHeight = 2; end
if isempty(p_nWidth), p_nWidth = 2; end
cAns = inputdlg({'Width (mm)' 'Height (mm)'}, 'Centering Box', 1, {num2str(p_nWidth) num2str(p_nHeight)});
if isempty(cAns), return, end
p_nWidth = str2num(cAns{1});
p_nHeight = str2num(cAns{2});
tUserData = get(handles.axes1, 'userData');
tUserData(1).vCenterBox = [p_nWidth p_nHeight];
set(handles.axes1, 'userData', tUserData)
grab_frame_Callback(hObject, eventdata, handles)
return
